1. 事务
数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成;可以理解成一次性处理操作了较大,复杂度较高的数据,并且一次性做完,例如在管理系统中,要删除一个人员,我们既要删除其基本资料,也要删除该人员的相关信息,文章,邮箱等等
关系型数据库中要使用事务必须满足ACID
;同时在MySQL中只有Innodb引擎才支持事务(右键表-选择改变表即可看到对应信息,如下),一般用于管理insert、update和delete操作
在MySQL中默认事务是自动提交(commit)的,因此要显式开启事务必须使用BEGIN、START、TRANSACTION或执行SET AUTOCOMMIT = 0 ,来禁止使用当前会话的commit
1.1 事务的使用
Spring Boot中,一般都会有 starter 或 web 依赖,这两个基础依赖中都已经包含了对于 spring-boot-starter-jdbc 或 spring-boot-starter-data-jpa 的依赖。 当我们使用了这两个依赖的时候,框架会自动默认分别注入 DataSourceTransactionManager 或 JpaTransactionManager 。 所以我们不需要任何额外配置就可以用
@Transactional
注解进行事务的使用
@Transactional
注解只能应用到public可见度的方法上,可以被应用于接口定义和接口方法,方法会覆盖类上面声明的事务
我们可以打开
@Transactional
接口
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";//value() 和 transactionManager() 都是用于指定使用哪个事务管理器
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;//传播行为
Isolation isolation() default Isolation.DEFAULT;//隔离级别
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;//超时时间
boolean readOnly() default false;//是否只读
Class<? extends Throwable>[] rollbackFor() default {};//指定异常回滚
String[] rollbackForClassName() default {};//指定异常名称
Class<? extends Throwable>[] noRollbackFor() default {};//指定什么异常不回滚
String[] noRollbackForClassName() default {};//指定什么异常不回滚的名称
下面用一个例子来讲解,例如用户新增需要插入用户表、用户与岗位关联表、用户与角色关联表,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作, 这样可以防止出现脏数据,就可以使用事务让它实现回退,我们只需在方法或类添加 @Transactional
注解即可;
//system.service.impl.SysUserServiceImpl的 `insertUser()` 新增保存用户信息方法
@Override
@Transactional
public int insertUser(SysUser user)
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色管理
insertUserRole(user);
return rows;
Transactional注解的常用属性表
一款管理系统,一般都配备有登录日志,这样管理者可以方便查看每个登录用户的登录情况,下面我们来看看是如何实现的吧~
首先我们打开 framework.web.service.SysLoginService,我们看到 login()
方法
if (captcha == null)
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));//采用的是异步执行
throw new CaptchaExpireException();
首先获取并判断验证码是否为空,这里会调用 AsyncManager异步任务管理器 ,其中有一个 execute()
方法,会调用定时任务TimerTask;其中还有一个 ScheduledExecutorService异步操作任务调度线程池,这个在 ThreadPoolConfig 中有配置,用于执行定时任务;还有一个 记录登录信息方法recordLogininfor()
,分别带有用户名、状态(已定义)和信息(从i18n中获取);后面的密码错误或者其他错误都会对其进行记录
在 framework.security.handle.LogoutSuccessHandlerImpl 中,这里退出操作同样也会记录日志,那接下来我们来看看这个 recordLogininfor()
方法都做了什么
public class AsyncFactory
private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");
public static TimerTask recordLogininfor(final String username, final String status, final String message,
final Object... args)//记录登录信息
//获取用户代理,这里的UserAgent需要导入UserAgentUtils工具jar包的
final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
/从而获得用户的ip地址,这个ip要放在线程外面
final String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
return new TimerTask()
@Override
public void run()
//根据ip获得本地地址
String address = AddressUtils.getRealAddressByIP(ip);
StringBuilder s = new StringBuilder();
s.append(LogUtils.getBlock(ip));
s.append(address);
s.append(LogUtils.getBlock(username));
s.append(LogUtils.getBlock(status));
s.append(LogUtils.getBlock(message));
// 打印信息到日志
sys_user_logger.info(s.toString(), args);
// 获取客户端操作系统
String os = userAgent.getOperatingSystem().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
// 封装对象
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setIpaddr(ip);
logininfor.setLoginLocation(address);
logininfor.setBrowser(browser);
logininfor.setOs(os);
logininfor.setMsg(message);
// 日志状态
if (Constants.LOGIN_SUCCESS.equals(status) || Constants.LOGOUT.equals(status))
logininfor.setStatus(Constants.SUCCESS);
else if (Constants.LOGIN_FAIL.equals(status))
logininfor.setStatus(Constants.FAIL);
// 插入数据
SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
public static TimerTask recordOper(final SysOperLog operLog)//操作日志记录
return new TimerTask()
@Override
public void run()
// 远程查询操作地点
operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog);
2.2 操作日志
同样的,一般管理系统也是标配操作日志的,这样方便管理员去查看每个登录用户做了什么操作,但是如果每记录一次操作,就去调用一次记录方法,收集参数,会造成大量的代码重复,但是我们希望代码中只有业务操作,这是就需要注解 @Log
来帮忙解决问题了
在需要被记录日志的controller方法上添加@Log注解,使用方法如下
@Log(title = "用户管理", businessType = BusinessType.INSERT)
支持参数如下
BusinessType
OTHER
操作功能(OTHER其他 INSERT新增 UPDATE修改 DELETE删除 GRANT授权 EXPORT导出 IMPORT导入 FORCE强退 GENCODE生成代码 CLEAN清空数据)
operatorType
OperatorType
MANAGE
操作人类别(OTHER其他 MANAGE后台用户 MOBILE手机端用户)
isSaveRequestData
boolean
是否保存请求的参数
我们可以打开项目中的注解接口 Log
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log//自定义操作日志记录注解
public String title() default "";//模块
public BusinessType businessType() default BusinessType.OTHER;//功能
public OperatorType operatorType() default OperatorType.MANAGE;//操作人类别
public boolean isSaveRequestData() default true;//是否保存请求的参数
关于自定义操作功能使用流程
在 BusinessType
中新增业务操作类型如
TEST,
在 sys_dict_data
字典数据表中初始化操作业务类型(即sql文件中创建字典表的语句)
insert into sys_dict_data values(25, 10, '测试', '10', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '测试操作');
在 Controller
中使用注解
@Log(title = "测试标题", businessType = BusinessType.TEST)
2.3 操作日志的实现
逻辑实现代码为 com.ruoyi.framework.aspectj.LogAspect ,我们进去可以看到,是一个切面类
// 配置织入点
@Pointcut("@annotation(com.ruoyi.common.annotation.Log)")//表明,当注解是Log时进去
继续往下看,注解中指定了一个返回值 returning = "jsonResult"
,这里指的是 管理系统中-系统管理-日志管理-操作日志-点击右边的详情弹出的窗口中的返回参数
再往下看,有两个方法,分别处理请求和处理异常的,里面都是调用了 handleLog()
方法,所以我们来看看其实现,大家自行查看其代码,结合着操作日志详细弹出窗口来理解
数据库日志记录,如url,ip地址等
异常检测,如果出现异常,则状态设置为错误和拿到错误消息
设置方法名称、请求方式和参数
保存到数据库
查询操作详细记录可以登录系统(系统管理-操作日志)