springboot(2.0)配置日志插入数据库
github:配置日志插入数据库操作
项目名:springboot-jpa-aop-Log
注意事项和重点
- 新增方法不要加try catch做异常捕获,不然无法触发@AfterThrowing异常处理方法(或者创建新的异常让@AfterThrowing捕获,同时手动做回滚)TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
- 注意在日志保存操作时使用 (@Transactional(propagation = Propagation.REQUIRES_NEW))配置另起新的事务,这样保存事务才不会被影响到。
1.项目结构和配置顺序
1.1创建实体类
表结构图:
1.2创建dao层接口
1.3配置注解和注解的实现类
1.4编写service进行注解
2.配置项目
主要讲解和注解实现类的配置,实体类和接口请在github上查看
注解
//作用位置。
@Target({ElementType.PARAMETER, ElementType.METHOD})
//注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysServiceLog {
/**
* 操作类型,类型为1:增加,2:删除,3:查询,4:修改,5:其他
* @return
*/
String oprlogType() default "";
/**
* 操作备注
* @return
*/
String oprlogRemark() default "";
//操作模块
String oprlogModule() default "";
}
注解类
@Component
@Aspect
public class SysServiceLogAspect {
@Resource
private SysOprlogService sysOprlogService;
private static String[] types = { "java.lang.Integer", "java.lang.Double",
"java.lang.Float", "java.lang.Long", "java.lang.Short",
"java.lang.Byte", "java.lang.Boolean", "java.lang.Char",
"java.lang.String", "int", "double", "long", "short", "byte",
"boolean", "char", "float" ,"java.util.HashMap"};
private static final Logger logger = LoggerFactory.getLogger(SysServiceLogAspect.class);
// Service层切点
@Pointcut("@annotation(SysServiceLog)")
public void serviceAspect() {
}
/**
* 返回通知,拦截方法执行成功后的结果,保存日志
* @param joinPoint 切点
* @param result 方法执行结果
*/
@AfterReturning(value="serviceAspect()",returning="result")
public void doServiceAfterReturning(JoinPoint joinPoint, Object result) {
SysOprlog sysOprlog = getSysOprlogInfo(joinPoint);
sysOprlog.setOprlogDescription(sysOprlog.getOprlogRemark() + "成功!");
sysOprlog.setOprlogStatus("1");
saveSysOprlog(sysOprlog);
}
/**
* 异常通知 用于拦截service层记录异常日志
* @param joinPoint
* @param e
*/
@AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
String className = joinPoint.getTarget().getClass().getName();// 获取所在实体类
String methodName = joinPoint.getSignature().getName();// 获取方法名
logger.error("执行失败!"+className+"->"+methodName+"失败原因:" + e.getMessage());
SysOprlog sysOprlog = getSysOprlogInfo(joinPoint);
sysOprlog.setOprlogDescription(sysOprlog.getOprlogRemark() + "失败!");
sysOprlog.setOprlogStatus("0");
saveSysOprlog(sysOprlog);
}
/**
* 获取SysOprlog的属性内容
* @param joinPoint
* @return
*/
private SysOprlog getSysOprlogInfo(JoinPoint joinPoint){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
// 读取session中的用户
// SysUser user = (SysUser) session.getAttribute("user");
//获取的是本地的IP地址 //PC-20140317PXKX/192.168.0.121
String ip = IPUtils.getIp(request);
String className = joinPoint.getTarget().getClass().getName();// 获取所在实体类
String methodName = joinPoint.getSignature().getName();// 获取方法名
SysOprlog sysOprlog = new SysOprlog();
try {
String paramContent = getLogParamContent(getFieldsName(this.getClass(), className, methodName), joinPoint);
//获取注解描述
SysServiceLog systemServiceLog = getServiceMethodDescription(joinPoint);
sysOprlog.setOprlogId(UUID.randomUUID().toString().replace("-", ""));
sysOprlog.setOprlogParam(paramContent);
sysOprlog.setOprlogRemark(systemServiceLog.oprlogRemark());
sysOprlog.setOprlogTime(new Date());
sysOprlog.setOprlogType(systemServiceLog.oprlogType());
sysOprlog.setOprlogModuleName(systemServiceLog.oprlogModule());
// sysOprlog.setOprlogLoginIp(ip);
} catch (Exception e) {
// 记录本地异常日志
logger.error("获取失败!失败原因:" + e.getMessage());
}
return sysOprlog;
}
/**
* 获取注解中对方法的描述信息,用于service层注解
* @param joinPoint 切点
* @return 方法描述
* @throws Exception
*/
@SuppressWarnings("rawtypes")
private SysServiceLog getServiceMethodDescription(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
SysServiceLog systemServiceLog = null;
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
systemServiceLog = method.getAnnotation(SysServiceLog.class);
break;
}
}
}
return systemServiceLog;
}
/**
* 获取调用方法参数的名称
* @param cls
* @param clazzName
* @param methodName
* @return
* @throws NotFoundException
*/
@SuppressWarnings("rawtypes")
private String[] getFieldsName(Class cls, String clazzName, String methodName) throws NotFoundException {
ClassPool pool = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(cls);
pool.insertClassPath(classPath);
CtClass cc = pool.get(clazzName);
CtMethod cm = cc.getDeclaredMethod(methodName);
MethodInfo methodInfo = cm.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
if (attr == null) {
// exception
}
String[] paramNames = new String[cm.getParameterTypes().length];
int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
for (int i = 0; i < paramNames.length; i++){
paramNames[i] = attr.variableName(i + pos); //paramNames即参数名
}
return paramNames;
}
/**
* 获取日志参数内容。格式:参数名+参数内容
* @param paramNames
* @param joinPoint
* @return
*/
private String getLogParamContent(String[] paramNames, JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
StringBuilder sb = new StringBuilder();
boolean clazzFlag = true;
for(int k=0; k<args.length; k++){
Object arg = args[k];
sb.append(paramNames[k]);
// 获取对象类型
String typeName = arg.getClass().getName();
for (String t : types) {
if (t.equals(typeName)) {
sb.append("=" + arg+";");
clazzFlag = false;
break;
}
}
if (clazzFlag) {
sb.append(getFieldsValue(arg));
}
}
return sb.toString();
}
/**
* 得到参数的值
* @param obj
*/
private String getFieldsValue(Object obj) {
Field[] fields = obj.getClass().getDeclaredFields();
String typeName = obj.getClass().getName();
for (String t : types) {
if(t.equals(typeName))
return "";
}
StringBuilder sb = new StringBuilder();
sb.append("[");
for (Field f : fields) {
f.setAccessible(true);
try {
for (String str : types) {
if (f.getType().getName().equals(str)){
if(f.get(obj) != null) {
sb.append(f.getName() + " = " + f.get(obj)+"; ");
}
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
sb.append("]");
return sb.toString();
}
/**
* hessian请求存储操作日志
* @param sysOprlog
*/
private void saveSysOprlog(SysOprlog sysOprlog){
try {
sysOprlogService.save(sysOprlog);
} catch (Exception e) {
e.printStackTrace();
logger.error("保存操作日志失败!失败原因:" + e.getMessage());
}
}
}
service配置重点
userService
@Service("userService")
public class UserService {
private Logger logger = LoggerFactory.getLogger(UserService.class);
@Resource
private UserRepository userRepository;
@Transactional
@SysServiceLog(oprlogType="1",oprlogRemark="新增/修改用户",oprlogModule = "系统管理")
public void insert(UsersEntity user){
// try {
userRepository.save(user);
// 异常测试代码
// ArrayList list = new ArrayList(2);
// System.out.println(list.get(11));
/* 注意使用try catch捕捉异常无法启动SysServiceLogAspect的@AfterThrowing方法。 */
// }catch (RuntimeException e){
// logger.info("用户保存失败");
// //就是这一句了,加上之后,抛了异常, 会回滚
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// }finally{
//
// }
}
}
SysOprlogService
@Service("sysOprLogService")
public class SysOprlogService {
@Resource
private SysOprLogRepository sysOprLogRepository;
// 配置另起新的事务,这样保存事务才不会被影响到。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(SysOprlog sysOprlog){
sysOprLogRepository.save(sysOprlog);
}
}
3.测试结果
测试方法
@Test
public void save(){
UsersEntity usersEntity = new UsersEntity();
usersEntity.setId(55);
usersEntity.setUserName("124");
usersEntity.setPassWord("124");
usersEntity.setNickName("124");
usersEntity.setUserSex("女");
userService.insert(usersEntity);
}
正常新增结果
错误新增结果
数据库结果
4.事务的说明
4.1事务回滚必要条件
- 被拦截方法-—— 注解式:方法或者方法所在类被@Transactional注解;
- 异常—— 该方法的执行过程必须出现异常,这样事务管理器才能被触发,并对此做出处理
指定异常—— 默认配置下,事务只会对Error与RuntimeException及其子类这些UNChecked异常,做出回滚。 一般的Exception这些Checked异常不会发生回滚(如果一般Exception想回滚要做出配置);
举例:实现一般异常的回滚:
注解式:@Transactional(rollbackFor=Exception.class)异常抛出—— 即方法中出现的指定异常,只有在被事务管理器捕捉到以后,事务才会据此进行事务回滚;
如:
1,不捕捉,会回滚
2.如果异常被try{}捕捉到,那么事务管理器就无法再捕捉异常,所以就无法做出反应,事务不回滚;(既catch(RuntimeException))
3.如果异常被try{}捕捉了,我们还可以在Catch(){}中throw new RuntimeException(),手动抛出运行时异常供事务管理器捕捉;(既catch(){ throw new RuntimeException(); } )
4.手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();