java编程开发之若依vue框架 --- 后台开发
注意
代码可能过时,但是思路是相似的,要想正常使用请查看最新代码实现。
代码只是展示了核心代码,将多个文件的不同代码在同一个地方展示,且部分函数没有定义展示。请依据实际情况编写
列表分页实现
- 前端基于
element
封装的分页组件 pagination(opens new window) - 后端基于
mybatis
的轻量级分页插件pageHelper(opens new window)
#前端调用实现
前端
html:
// 页面添加分页组件,传入分页变量 <pagination v-show="total>0" //整个分页组件中,通过 v-show 属性来判断是否展示分页组件,如果后端返回的总记录数 total 大于 0,就展示分页组件。 :total="total" //分页组件中,通过 total 属性来获取后端返回的总记录数,从而计算出总页数,并在分页中展示。:表示传递给组件的参数 :page.sync="queryParams.pageNum" //同样的,通过 :page.sync 和 :limit.sync 属性,来绑定当前页码和每页数据数量。这些属性是和组件内部的状态进行双向绑定的,当页码或每页数据数量发生变化时,组件内部会自动刷新。 :limit.sync="queryParams.pageSize" @pagination="getList" //最后,通过 @pagination 属性,绑定一个自定义回调函数 getList,当用户点击分页组件中的页码时,将会调用该函数,来重新获取当前页码的数据。 />
js:
// 定义分页变量
queryParams: {
pageNum: 1,
pageSize: 10
},
methods: {
getList(newPage) { // 定义 getList 函数
this.queryParams.pageNum = newPage; // 更新页码参数
listUser(this.queryParams).then(response => { // 调用接口获取数据
this.userList = response.rows;
this.total = response.total;
});
}
// 调用后端api方法,传入参数 获取结果查询结构
listUser(this.queryParams).then(response => {
this.userList = response.rows; //这里面保存着响应的分页后的数据
this.total = response.total; //总数
}
);
}
后台
@PostMapping("/list") //定义/list接口,用于查询出列表 @ResponseBody //返回非页面数据 public TableDataInfo list(User user) //TableDataInfo 可以理解为分页后的数据结构,user用于条件查询
{
startPage(); // 此方法配合前端完成自动分页pagehelper,前端会发送当前页和页面大小,这里会保存该数据,
List<User> list = userService.selectUserList(user); //要分页的list
return getDataTable(list);//将list分页
}
常见坑点1:selectPostById莫名其妙的分页。例如下面这段代码
startPage(); //创建一些分页参数 List<User> list; if(user != null){ list = userService.selectUserList(user); } else { list = new ArrayList<User>(); } Post post = postService.selectPostById(1L); return getDataTable(list);
原因分析:这种情况下由于user存在null的情况,就会导致pageHelper生产了一个分页参数,但是没有被selectUserList执行时消费,这个参数就会一直保留在这个线程上。 当这个线程的参数再次被selectPostById错误的使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。因此分页函数startPage
应该与要分页的list紧密结合
上面这个代码,应该写成下面这个样子才能保证安全。
List<User> list; if(user != null){ startPage(); list = userService.selectUserList(user); } else { list = new ArrayList<User>(); } Post post = postService.selectPostById(1L); return getDataTable(list);
注意
如果改为其他数据库需修改配置application.yml文件中的属性helperDialect=你的数据库
表格导入导出
在实际开发中经常需要使用导入导出功能来加快数据的操作。在项目中可以使用注解来完成此项功能。 在需要被导入导出的实体类属性添加@Excel
注解,目前支持参数如下:
注解参数说明
参数 | 类型 | 默认值 | 描述 |
---|---|---|---|
sort | int | Integer.MAX_VALUE | 从小到大排序 |
name | String | 空 | 列名 |
dateFormat | String | 空 | 日期格式, 如: yyyy-MM-dd |
dictType | String | 空 | 如果是字典类型,请设置字典的type值 (如: sys_user_sex) |
readConverterExp | String | 空 | 读取内容转表达式 (如: 0=男,1=女,2=未知) |
separator | String | , | 分隔符,读取字符串组内容 |
scale | int | -1 | BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化) |
roundingMode | int | BigDecimal.ROUND_HALF_EVEN | BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN |
celltype | Enum | Type.STRING | 导出类型(0数字 1字符串 2图片) |
height | String | 14 | 导出时在excel中每个列的高度 单位为字符 |
width | String | 16 | 导出时在excel中每个列的宽 单位为字符 |
suffix | String | 空 | 文字后缀,如% 90 变成90% |
defaultValue | String | 空 | 当值为空时,字段的默认值 |
prompt | String | 空 | 提示信息 |
combo | String | Null | 设置只能选择不能输入的列内容 |
headerBackgroundColor | Enum | IndexedColors.GREY_50_PERCENT | 导出列头背景色IndexedColors.XXXX |
headerColor | Enum | IndexedColors.WHITE | 导出列头字体颜色IndexedColors.XXXX |
backgroundColor | Enum | IndexedColors.WHITE | 导出单元格背景色IndexedColors.XXXX |
color | Enum | IndexedColors.BLACK | 导出单元格字体颜色IndexedColors.XXXX |
targetAttr | String | 空 | 另一个类中的属性名称,支持多级获取,以小数点隔开 |
isStatistics | boolean | false | 是否自动统计数据,在最后追加一行统计数据总和 |
type | Enum | Type.ALL | 字段类型(0:导出导入;1:仅导出;2:仅导入) |
align | Enum | HorizontalAlignment.CENTER | 导出对齐方式HorizontalAlignment.XXXX |
handler | Class | ExcelHandlerAdapter.class | 自定义数据处理器 |
args | String[] | {} | 自定义数据处理器参数 |
导出
前端
html:
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport" >导出</el-button>
js: // 查询参数 queryParams queryParams: { pageNum: 1, pageSize: 10, userName: undefined }, // 导出接口exportUser import { exportUser } from "@/api/system/user"; /** 导出按钮操作 */ handleExport() { const queryParams = this.queryParams; this.$confirm('是否确认导出所有用户数据项?', "警告", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }).then(function() { return exportUser(queryParams); }).then(response => { this.download(response.msg); }).catch(function() {}); }
后端
在实体变量上添加@Excel注解
在实体变量上添加@Excel注解
@Excel(name = "用户序号", prompt = "用户编号")
private Long userId;
@Excel(name = "用户名称")
private String userName;
@Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
private String sex;
@Excel(name = "用户头像", cellType = ColumnType.IMAGE)
private String avatar;
@Excel(name = "帐号状态", dictType = "sys_normal_disable")
private String status;
@Excel(name = "最后登陆时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date loginDate;
在Controller添加导出方法
@Log(title = "用户管理", businessType = BusinessType.EXPORT) @PreAuthorize("@ss.hasPermi('system:user:export')") @GetMapping("/export") public AjaxResult export(SysUser user) { List<SysUser> list = userService.selectUserList(user); ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class); return util.exportExcel(list, "用户数据"); }
导入
前端
html:
<!-- 导入按钮 -->
<el-button type="info" icon="el-icon-upload2" size="mini" @click="handleImport" <!-- 触发函数 -->
>导入</el-button>
<!-- 点击导入按钮后对话框
-->
<!-- 用户导入对话框 -->
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px">
<el-upload
ref="upload"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url + '?updateSupport=' + upload.updateSupport"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<!--
`:title="upload.title"`:定义对话框的标题,值为 upload.title。
`:visible.sync="upload.open"`:控制对话框是否可见,值为 upload.open。这里都是数据控制试图显示
`ref="upload"`:定义 el-upload 组件的引用名。
`:limit="1"`:上传文件数量的限制,值为 1。
`:headers="upload.headers"`:上传文件时需要发送的头部信息。
`accept=".xlsx, .xls"`:限制上传的文件类型,值为 .xlsx 和 .xls。
`:action="upload.url + '?updateSupport=' + upload.updateSupport"`:上传文件时需要发送的后端接口地址,upload.url 为接口地址,upload.updateSupport 表示是否更新已经存在的用户数据。
`:disabled="upload.isUploading"`:控制上传按钮是否可用,值为 upload.isUploading,表示当前是否正在上传文件。
`:on-progress="handleFileUploadProgress"`:监听文件上传过程中的进度,调用 handleFileUploadProgress 函数处理。
`:on-success="handleFileSuccess"`:监听文件上传成功事件,调用 handleFileSuccess 函数处理。
`:auto-upload="false"`:是否自动上传,该属性为 false,则需要手动点击上传按钮才触发上传操作。
`drag`:属性 drag 表示开启拖拽上传的功能。
-->
<i class="el-icon-upload"></i> <!-- 上传图标,使用了饿了么 UI 库中的上传图标。 -->
<div class="el-upload__text">
将文件拖到此处,或
<em>点击上传</em>
</div>
<div class="el-upload__tip" slot="tip">
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户数据
<el-link type="info" style="font-size:12px" @click="importTemplate">下载模板</el-link>
</div>
<div class="el-upload__tip" style="color:red" slot="tip">提示:仅允许导入“xls”或“xlsx”格式文件!</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm">确 定</el-button>
<el-button @click="upload.open = false">取 消</el-button>
</div>
</el-dialog>
js: import { getToken } from "@/utils/auth"; // 用户导入参数 upload: { // 是否显示弹出层(用户导入) open: false, // 弹出层标题(用户导入) title: "", // 是否禁用上传 isUploading: false, // 是否更新已经存在的用户数据 updateSupport: 0, // 设置上传的请求头部 headers: { Authorization: "Bearer " + getToken() }, // 上传的地址 url: process.env.VUE_APP_BASE_API + "/system/user/importData" }, // 导入模板接口importTemplate import { importTemplate } from "@/api/system/user"; /** 导入按钮操作 */ handleImport() { this.upload.title = "用户导入"; //导入对话框标题 this.upload.open = true; //导入对话框可见 }, /** 下载模板操作 */ importTemplate() { importTemplate().then(response => { this.download(response.msg); }); }, // 文件上传中处理 handleFileUploadProgress(event, file, fileList) { this.upload.isUploading = true; }, // 文件上传成功处理 handleFileSuccess(response, file, fileList) { this.upload.open = false; this.upload.isUploading = false; this.$refs.upload.clearFiles(); this.$alert(response.msg, "导入结果", { dangerouslyUseHTMLString: true }); this.getList(); }, // 提交上传文件 submitFileForm() { this.$refs.upload.submit(); }
后端
在实体变量上添加@Excel注解,默认为导出导入,也可以单独设置仅导入Type.IMPORT @Excel(name = "用户序号") private Long id; @Excel(name = "部门编号", type = Type.IMPORT) private Long deptId; @Excel(name = "用户名称") private String userName; /** 导出部门多个对象 */ @Excels({ @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT), @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT) }) private SysDept dept; /** 导出部门单个对象 */ @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT) private SysDept dept; 在Controller添加导入方法,updateSupport属性为是否存在则覆盖(可选) @Log(title = "用户管理", businessType = BusinessType.IMPORT) @PostMapping("/importData") public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception { ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class); List<SysUser> userList = util.importExcel(file.getInputStream()); LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); String operName = loginUser.getUsername(); String message = userService.importUser(userList, updateSupport, operName); return AjaxResult.success(message); } @GetMapping("/importTemplate") public AjaxResult importTemplate() { ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class); return util.importTemplateExcel("用户数据"); }
提示
也可以直接到main运行此方法测试。
InputStream is = new FileInputStream(new File("D:\\test.xlsx")); ExcelUtil<Entity> util = new ExcelUtil<Entity>(Entity.class); List<Entity> userList = util.importExcel(is);
定义表头
导入后端
public AjaxResult export(SysUser user) { List<SysUser> list = userService.selectUserList(user); ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class); return util.exportExcel(list, "表名", "表头"); }
导出后端
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
{
ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
List<SysUser> userList = util.importExcel(file.getInputStream(), 1); //其中1
表示标题占用行数,根据实际情况填写。
String operName = SecurityUtils.getUsername();
String message = userService.importUser(userList, updateSupport, operName);
return AjaxResult.success(message);
}
表格处理器
有时候我们希望数据展现为一个特殊的格式,或者需要对数据进行其它处理。Excel注解提供了自定义数据处理器以满足各种业务场景。而实现一个数据处理器也是非常简单的。如下:
1、在实体类用Excel注解handler属性指定自定义的数据处理器 public class User extends BaseEntity { @Excel(name = "用户名称", handler = MyDataHandler.class, args = { "aaa", "bbb" }) private String userName; } 2、编写数据处理器MyDataHandler继承ExcelHandlerAdapter,返回值为处理后的值。 public class MyDataHandler implements ExcelHandlerAdapter { @Override public Object format(Object value, String[] args) { // value 为单元格数据值 // args 为excel注解args参数组 return value; } }
表格隐藏列
有时候我们希望对列信息根据业务去动态显示,那么我们可以进行如下处理。 示例:对用户进行条件判断,符合条件则隐藏属性。导出的文件则不会显示此列信息。 @PostMapping("/export") public void export(HttpServletResponse response, SysUser user) { List<SysUser> list = userService.selectUserList(user); ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class); if (条件A) { // 不显示用户ID(单个) util.hideColumn("userId"); } else if (条件B) { // 不显示用户名称、用户手机(多个) util.hideColumn("userId", "phonenumber"); } } else if (条件C) { // 不显示用户邮箱、部门名称(子对象) util.hideColumn("email", "dept.deptName"); } util.exportExcel(response, list, "用户数据"); }
表格子对象
有时候对象里面还包含集合列表,例如用户管理包含多个角色需要导出,那么我们可以进行如下处理。
SysUser.java public class SysUser { @Excel(name = "用户编号", cellType = ColumnType.NUMERIC, width = 20, needMerge = true) private String userId; @Excel(name = "用户名称", width = 20, needMerge = true) private String userName; @Excel(name = "邮箱", width = 20, needMerge = true) private String email; @Excel(name = "角色") private List<SysRole> roles; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public List<SysRole> getRoles() { return roles; } public void setRoles(List<SysRole> roles) { this.roles = roles; } } SysRole.java public class SysRole { @Excel(name = "角色编号", cellType = ColumnType.NUMERIC) private String roleId; @Excel(name = "角色名称") private String roleName; @Excel(name = "角色字符") private String roleKey; public String getRoleId() { return roleId; } public void setRoleId(String roleId) { this.roleId = roleId; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getRoleKey() { return roleKey; } public void setRoleKey(String roleKey) { this.roleKey = roleKey; } } 测试验证 public class Test { public static void main(String[] args) throws IOException { List<SysUser> userList = new ArrayList<SysUser>(); SysUser user1 = new SysUser(); List<SysRole> roles1 = new ArrayList<SysRole>(); SysRole role1 = new SysRole(); role1.setRoleId("1"); role1.setRoleName("超级管理员"); role1.setRoleKey("admin_key"); SysRole role2 = new SysRole(); role2.setRoleId("2"); role2.setRoleName("普通角色"); role2.setRoleKey("common_key"); SysRole role3 = new SysRole(); role3.setRoleId("3"); role3.setRoleName("测试角色"); role3.setRoleKey("test_key"); SysRole role4 = new SysRole(); role4.setRoleId("4"); role4.setRoleName("查询角色"); role4.setRoleKey("query_key"); roles1.add(role1); roles1.add(role2); roles1.add(role3); roles1.add(role4); user1.setUserId("1"); user1.setUserName("admin"); user1.setEmail("ry@qq.com"); user1.setRoles(roles1); userList.add(user1); SysUser user2 = new SysUser(); List<SysRole> roles2 = new ArrayList<SysRole>(); SysRole role21 = new SysRole(); role21.setRoleId("4"); role21.setRoleName("研发角色"); role21.setRoleKey("yanfa_key"); SysRole role22 = new SysRole(); role22.setRoleId("5"); role22.setRoleName("销售角色"); role22.setRoleKey("xiaoshou_key"); roles2.add(role21); roles2.add(role22); user2.setUserId("2"); user2.setUserName("ry"); user2.setEmail("admin@qq.com"); user2.setRoles(roles2); userList.add(user2); SysUser user3 = new SysUser(); List<SysRole> roles3 = new ArrayList<SysRole>(); SysRole role31 = new SysRole(); role31.setRoleId("4"); role31.setRoleName("张三角色"); role31.setRoleKey("zs_key"); SysRole role32 = new SysRole(); role32.setRoleId("5"); role32.setRoleName("李四角色"); role32.setRoleKey("ls_key"); roles3.add(role31); roles3.add(role32); user3.setUserId("3"); user3.setUserName("test"); user3.setEmail("test@qq.com"); user3.setRoles(roles3); userList.add(user3); ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class); AjaxResult ajax = util.exportExcel(userList, "用户数据", "用户数据"); System.out.println(ajax.toString()); } }
文件上传下载
上传
首先创建一张上传文件的表,例如:
drop table if exists sys_file_info; create table sys_file_info ( file_id int(11) not null auto_increment comment '文件id', file_name varchar(50) default '' comment '文件名称', file_path varchar(255) default '' comment '文件路径', primary key (file_id) ) engine=innodb auto_increment=1 default charset=utf8 comment = '文件信息表';
前端
html:
<el-upload
ref="upload" <!-- 给上传组件一个引用名 -->
:limit="1" <!-- 限制最大上传文件数为1 -->
accept=".jpg, .png" <!-- 只允许上传 .jpg 和 .png 格式的文件 -->
:action="upload.url" <!-- 文件上传的接口地址 -->
:headers="upload.headers" <!-- 上传请求头,用于验证用户身份等 -->
:file-list="upload.fileList" <!-- 已选中文件列表 -->
:on-progress="handleFileUploadProgress" <!-- 文件上传进度回调函数 -->
:on-success="handleFileSuccess" <!-- 文件上传成功回调函数 -->
:auto-upload="false"> <!-- 是否自动开始上传 -->
<el-button slot="trigger" size="small" type="primary">选取文件</el-button> <!-- 上传按钮,点击后弹出文件选择器 -->
<el-button style="margin-left: 10px;" size="small" type="success" :loading="upload.isUploading" @click="submitUpload">上传到服务器</el-button> <!-- 上传按钮,点击后将上传队列中的文件传输到服务器 -->
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div> <!-- 提示信息,显示文件上传的格式限制和大小限制 -->
</el-upload>
js:
import { getToken } from "@/utils/auth";
data
中添加属性
// 上传参数
upload: {
// 是否禁用上传
isUploading: false,
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
// 上传的地址
url: process.env.VUE_APP_BASE_API + "/common/upload",
// 上传的文件列表
fileList: []
},
data
中添加属性
// 上传参数
upload: {
// 是否禁用上传
isUploading: false,
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
// 上传的地址
url: process.env.VUE_APP_BASE_API + "/common/upload",
// 上传的文件列表
fileList: []
},
新增按钮和修改按钮操作对应处理fileList
参数
handleAdd() {
...
this.upload.fileList = [];
}
handleUpdate(row) {
...
this.upload.fileList = [{ name: this.form.fileName, url: this.form.filePath }];
}
添加对应事件
// 文件提交处理
submitUpload() {
this.$refs.upload.submit(); //调用upload组件的submit方法
},
// 文件上传中处理
handleFileUploadProgress(event, file, fileList) {
this.upload.isUploading = true;
},
// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
this.upload.isUploading = false;
this.form.filePath = response.url;
this.msgSuccess(response.msg);
}
后台
在SysFileInfoController添加对应上传方法
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(@RequestParam("file") MultipartFile file, SysFileInfo fileInfo) throws IOException
{
// 上传文件路径
String filePath = RuoYiConfig.getUploadPath();
// 上传并返回新文件名称
String fileName = FileUploadUtils.upload(filePath, file);
fileInfo.setFilePath(fileName); //设置上传路径
return toAjax(sysFileInfoService.insertSysFileInfo(fileInfo)); //保存上传代码
}
下载
前端
添加对应按钮和事件 <el-button size="mini" type="text" icon="el-icon-edit" @click="handleDownload(scope.row)" >下载</el-button> // 文件下载处理 handleDownload(row) { var name = row.fileName; var url = row.filePath; var suffix = url.substring(url.lastIndexOf("."), url.length); const a = document.createElement('a') a.setAttribute('download', name + suffix) a.setAttribute('target', '_blank') a.setAttribute('href', url) a.click() }
权限注解
Spring Security
提供了Spring EL
表达式,允许我们在定义接口访问的方法上面添加注解,来控制访问权限。
#权限方法
@PreAuthorize
注解用于配置接口要求用户拥有某些权限才可访问,它拥有如下方法
方法 | 参数 | 描述 |
---|---|---|
hasPermi | String | 验证用户是否具备某权限 |
lacksPermi | String | 验证用户是否不具备某权限,与 hasPermi逻辑相反 |
hasAnyPermi | String | 验证用户是否具有以下任意一个权限 |
hasRole | String | 判断用户是否拥有某个角色 |
lacksRole | String | 验证用户是否不具备某角色,与 isRole逻辑相反 |
hasAnyRoles | String | 验证用户是否具有以下任意一个角色,多个逗号分隔 |
#使用示例
其中@ss代表的是PermissionService (opens new window)服务,对每个接口拦截并调用PermissionService的对应方法判断接口调用者的权限。
数据权限示例。
// 符合system:user:list权限要求
@PreAuthorize("@ss.hasPermi('system:user:list')")
// 不符合system:user:list权限要求
@PreAuthorize("@ss.lacksPermi('system:user:list')")
// 符合system:user:add或system:user:edit权限要求即可
@PreAuthorize("@ss.hasAnyPermi('system:user:add,system:user:edit')")
角色权限示例。
// 属于user角色
@PreAuthorize("@ss.hasRole('user')")
// 不属于user角色
@PreAuthorize("@ss.lacksRole('user')")
// 属于user或者admin之一
@PreAuthorize("@ss.hasAnyRoles('user,admin')")
权限提示
超级管理员拥有所有权限,不受权限约束
公开接口
如果有些接口是不需要验证权限可以公开访问的,这个时候就需要我们给接口放行。
使用注解方式,只需要在Controller
的类或方法上加入@Anonymous
该注解即可
// @PreAuthorize("@ss.xxxx('....')") 注释或删除掉原有的权限注解 @Anonymous @GetMapping("/list") public List<SysXxxx> list(SysXxxx xxxx) { return xxxxList; }
事务管理
这里的事务指的是数据库中的,表示一组sql要么同时成功要么同时失败回滚。新建的Spring Boot项目中,一般都会引用spring-boot-starter或者spring-boot-starter-web,而这两个起步依赖中都已经包含了对于spring-boot-starter-jdbc或spring-boot-starter-data-jpa的依赖。 当我们使用了这两个依赖的时候,框架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。 所以我们不需要任何额外配置就可以用@Transactional注解进行事务的使用。
提示
@Transactional注解只能应用到public可见度的方法上,可以被应用于接口定义和接口方法,方法会覆盖类上面声明的事务。
例如用户新增需要插入用户表、用户与岗位关联表、用户与角色关联表,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作, 这样可以防止出现脏数据,就可以使用事务让它实现回退。
做法非常简单,我们只需要在方法或类添加@Transactional注解即可。
@Transactional
public int insertUser(User user)
{
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色管理
insertUserRole(user);
return rows;
}
`@Transactional` 注解是 Spring 框架提供的一个事务控制注解,用于管理事务的开启和提交等行为。其作用是让带有该注解的方法在执行时被 Spring AOP 拦截,自动开启事务并在方法执行结束时提交或回滚事务。
关于 `@Transactional` 注解的限制和使用规则,以下是一些需要注意的事项:
- 该注解只能应用于 `public` 可见度的方法上,因为只有 public 方法才能被 Spring AOP 拦截并代理。如果将该注解应用于非 public 方法上,Spring AOP 将无法拦截该方法的调用,因此该注解的作用也就失效了。
- 该注解可以被应用于接口定义和接口方法,也可以被应用于类、类方法、抽象类、抽象类方法、父类方法、本类方法等不同的位置。当一个方法被声明为 `@Transactional` 之后,该方法会覆盖类上面声明的事务。也就是说,如果一个类被声明为 `@Transactional`,则该类中的所有方法都将使用该注解声明的事务设置,但是如果类中的某个方法被声明为 `@Transactional`,则该方法会使用方法上的事务设置,而忽略类上面声明的事务。
- 该注解提供了一些属性,可以用于控制事务的隔离级别、传播行为、超时时间、只读性质等。通过设置不同的属性值,我们可以对事务进行更加精细的控制和管理。
- `@Transactional` 注解的使用需要在 Spring 容器中开启事务管理功能,通常采用 `@EnableTransactionManagement` 注解或 XML 配置的方式来开启。如果没有在容器中开启事务管理功能,则 `@Transactional` 注解将不会生效。
总之,`@Transactional` 注解是一个非常实用的事务管理工具,能够帮助我们简化事务管理的代码,提高代码的可读性和可维护性。但是它的使用也需要注意一些细节和规则,以避免出现不必要的错误和异常情况。
常见坑点1:遇到检查异常时,事务开启,也无法回滚。 例如下面这段代码,用户依旧增加成功,并没有因为后面遇到检查异常而回滚!!
@Transactional
public int insertUser(User user) throws Exception
{
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色管理
insertUserRole(user);
// 模拟抛出SQLException异常
boolean flag = true;
if (flag)
{
throw new SQLException("发生异常了..");
}
return rows;
}
原因分析:因为Spring的默认的事务规则是遇到运行异常(RuntimeException)和程序错误(Error)才会回滚。如果想针对检查异常进行事务回滚,可以在@Transactional注解里使用 rollbackFor属性明确指定异常。
例如下面这样,就可以正常回滚:
@Transactional(rollbackFor = Exception.class)
public int insertUser(User user) throws Exception
{
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色管理
insertUserRole(user);
// 模拟抛出SQLException异常
boolean flag = true;
if (flag)
{
throw new SQLException("发生异常了..");
}
return rows;
}
常见坑点2: 在业务层捕捉异常后,发现事务不生效。 这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,Spring自然不知道这里有错,更不会主动去回滚数据。
例如:下面这段代码直接导致用户新增的事务回滚没有生效。
@Transactional
public int insertUser(User user) throws Exception
{
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色管理
insertUserRole(user);
// 模拟抛出SQLException异常
boolean flag = true;
if (flag)
{
try
{
// 谨慎:尽量不要在业务层捕捉异常并处理
throw new SQLException("发生异常了..");
}
catch (Exception e)
{
e.printStackTrace();
}
}
return rows;
}
推荐做法:在业务层统一抛出异常,然后在控制层统一处理。
@Transactional
public int insertUser(User user) throws Exception
{
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色管理
insertUserRole(user);
// 模拟抛出SQLException异常
boolean flag = true;
if (flag)
{
throw new RuntimeException("发生异常了..");
}
return rows;
}
Transactional
注解的常用属性表:
属性 | 说明 |
---|---|
propagation | 事务的传播行为,默认值为 REQUIRED。 |
isolation | 事务的隔离度,默认值采用 DEFAULT |
timeout | 事务的超时时间,默认值为-1,不超时。如果设置了超时时间(单位秒),那么如果超过该时间限制了但事务还没有完成,则自动回滚事务。 |
read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollbackFor | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。{xxx1.class, xxx2.class,……} |
noRollbackFor | 抛出 no-rollback-for 指定的异常类型,不回滚事务。{xxx1.class, xxx2.class,……} |
.... |
提示
事务的传播机制是指如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。 即:在执行一个@Transactinal注解标注的方法时,开启了事务;当该方法还在执行中时,另一个人也触发了该方法;那么此时怎么算事务呢,这时就可以通过事务的传播机制来指定处理方式。
TransactionDefinition
传播行为的常量:
常量 | 含义 |
---|---|
TransactionDefinition.PROPAGATION_REQUIRED | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。 |
TransactionDefinition.PROPAGATION_REQUIRES_NEW | 创建一个新的事务,如果当前存在事务,则把当前事务挂起。 |
TransactionDefinition.PROPAGATION_SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 |
TransactionDefinition.PROPAGATION_NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 |
TransactionDefinition.PROPAGATION_NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常。 |
TransactionDefinition.PROPAGATION_MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 |
TransactionDefinition.PROPAGATION_NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。 |
#
上传成功后需要预览可以对该属性格式化处理
{
field : 'filePath',
title: '文件预览',
formatter: function(value, row, index) {
return $.table.imageView(value);
}
},
这个代码块是典型的表格列格式化代码,应该将它写在完成表格的渲染逻辑中,例如 Vue.js 的 template 或 React.js 的 render() 函数中。在这里,您可以使用前端框架提供的表格组件(例如 Element、Ant Design、Bootstrap Table 等),然后定义表格的列及其对应的数据字段。对于需要格式化的数据字段,您只需要在列的定义中使用 `formatter` 属性并指定对应的处理函数即可。
以 Bootstrap Table 为例,在 Vue.js 组件中定义表格的列时,可以将上述代码段放在 columns 属性中,例如:
```javascript
<template>
<div>
<bootstrap-table :data="tableData" :columns="tableColumns" />
</div>
</template>
<script>
export default {
data() {
return {
tableData: [...], // 表格数据
tableColumns: [ // 表格列定义
{
field: 'id',
title: 'ID'
},
{
field: 'name',
title: 'Name'
},
{
field: 'filePath',
title: 'File Path',
formatter: function(value, row, index) {
return $.table.imageView(value);
}
}
]
}
}
}
</script>
```
在这个例子中,我们将文件预览列的格式化代码放在了字段定义中,设置了相应的 formatter 处理函数。这样在表格渲染时,会对数据中的 filePath 字段进行格式化处理,显示文件预览效果。
需要注意的是,这个例子中使用了 Bootstrap Table 组件,需要确保在 Vue.js 组件中正确引用了 Bootstrap Table 的前端库和样式文件,才能正常渲染和使用该表格。
在执行该代码后,如果数据中 `filePath` 字段有值,会对该字段进行预览处理并展示在表格中。预览的效果由 `$.table.imageView()` 方法实现,该方法将文件路径转换为预览图片、音频、视频等。
例如,如果您上传了一个名为 `test.png` 的图片文件,后端处理后将返回一个 JSON 对象,其中包含了文件路径 `filePath` 值为 `/uploads/image/test/20201110/test.png`。在该列定义中加入 `formatter` 函数并调用 `$.table.imageView()` 方法进行展示后,表格的预览列将展示出该图片的预览效果。
预览的具体效果会根据文件的类型以及前端库的实现方式而异。例如,对于图片文件,可以展示图片的缩略图或完整图像;对于音频文件,可以在表格中播放音频文件,并提供基本的控制功能;对于视频文件,可以在单元格中展示一个嵌入式的视频播放器,并提供控制和全屏等功能。根据您所选择的前端库和相关配置,预览效果可能有所不同。
如需对文件格式控制,设置application.yml中的multipart属性
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
注意:如果只是单纯的上传一张图片没有其他参数可以使用通用方法 /common/upload
请求处理方法 com.ruoyi.web.controller.common.CommonController
下载
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律