1.Mybatis整合Logback 2.SqlSession为什么可以提交事务 3.insert和delete底层实现的方式 4.完成超市账单管理系统的登陆功能
1.springmvc,mybatis,logback整合相关
1.利用spring-aop实现日志自动化
1.1添加相关依赖(spring-aop,aspectj)
<span style="white-space:pre"> </span><dependency>
<span style="white-space:pre"> </span><groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
1.2 spring-mvc.xml添加配置
<span style="white-space:pre"> </span><aop:aspectj-autoproxy proxy-target-class="true"/>
这条配置是将自动生成代理,proxy-target-class="true"配置是为实现类生成代理,若不加此项只能为接口生成代理
1.3 配置切面
@Component
@Aspect
public class LogAspect {
private Log log = LogFactory.getLog(LogAspect.class);
@Pointcut("execution(* com.jiechengkeji.manager..*(..))")
public void aspect() {
}
@Around("aspect()")
public Object around(ProceedingJoinPoint point) {
long start = System.currentTimeMillis();
String uuid = UUID.randomUUID().toString();//由于开始记录和结束记录会错开,用UUID进行配对
String before = String.format("%s.%s()--ARGS:%s (%s)",
point.getTarget().getClass().getSimpleName(),
MethodSignature.class.cast(point.getSignature()).getMethod().getName(),
Arrays.toString(point.getArgs()),
uuid);
log.info(before);
Object result = null;
try {
result = point.proceed();
} catch (Throwable e) {
log.error(e);
}
String after = String.format("%s.%s()--RETURNS:%s TIME:%s ms (%s)",
point.getTarget().getClass().getSimpleName(),
MethodSignature.class.cast(point.getSignature()).getMethod().getName(),
result,
System.currentTimeMillis() - start,
uuid);
log.info(after);
return result;
}
}
没啥说的 基本上看代码就能明白,不要忘记添加@Component注解。之前我以为和 @Controller,@Service一样是包含@Component注解的,没想到不是,排查了很长时间。
1.4 配置logback.xml
<!-- aop记日志的appender -->
<appender name="aop" class="ch.qos.logback.core.rolling.RollingFileAppender">
<FileNamePattern>${log.base}/${log.methodModuleName}%d{yyyy-MM-dd}.%i.log</FileNamePattern> <!-- 设置日志不超过${log.max.size}时的保存路径,注意如果
是web项目会保存到Tomcat的bin目录 下 -->
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件。 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log.base}/${log.methodModuleName}%d{yyyy-MM-dd}.%i.log.zip
</FileNamePattern>
<!-- 文件输出日志 (文件大小策略进行文件输出,超过指定大小对文件备份) -->
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${log.max.size}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 日志输出的文件的格式 -->
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%date{HH:mm:ss} [%-5level] %msg%n</Pattern>
</layout>
</appender>
<span style="white-space:pre"> </span><logger name="com.jiechengkeji.manager.aop.LogAspect" additivity="false">
<span style="white-space:pre"> </span><level value="INFO" />
<span style="white-space:pre"> </span><appender-ref ref="aop" />
<span style="white-space:pre"> </span></logger>
2. 整合mybatis
2.1 调整mybatis的config.xml
<settings>
<setting name="logPrefix" value="dao."/> <!-- dao. 是logger的name .不可省略-->
<setting name="cacheEnabled" value="false" />
<setting name="defaultExecutorType" value="REUSE" />
<setting name="logImpl" value="COMMONS_LOGGING" />
</settings>
2.2 调整logback.xml
<logger name="dao" level="DEBUG">
<!--daoFILE为实际定义的appender-->
<appender-ref ref="aop" />
</logger>
3.效果
1.@Pointcut("execution(* com.jiechengkeji.manager..*(..))")中定义的每一个切入点 执行前执行后都会打印日志
2.mybatis的日志信息也会打印到aop appender指定的文件中
示例:
</pre>13:12:35 [INFO ] CityService.getCityNameById()--ARGS:[-1] (48ded28b-2773-4846-897e-ef14beee273b)13:12:35 [DEBUG] ooo Using Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@386f61ed]]]13:12:35 [DEBUG] ==> Preparing: select name from dy_city where id = ? ; 13:12:35 [DEBUG] ==> Parameters: -1(Integer)13:12:35 [DEBUG] <== Total: 013:12:35 [INFO ] CityService.getCityNameById()--RETURNS:null TIME:8 ms (48ded28b-2773-4846-897e-ef14beee273b)13:12:35 [INFO ] LoginController.doLogin()--RETURNS:com.jiechengkeji.manager.util.AsyncResult@23b16363 TIME:339 ms (15ab451d-fa2e-42e1-adbe-54e106d77705)<p></p><div style="top:1283px"><pre name="code" class="html"> @Pointcut("execution(* com.jiechengkeji.manager..*(..))")
————————————————
版权声明:本文为CSDN博主「史蒂芬的速度」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/winnerwxc/article/details/51612493
2.SqlSession为什么可以提交事务
如题目所示,本小章节讨论为什么SqlSession的commit会造成事物的提交
首先先看SqlSession的commit()他是一个接口的方法,所以去他的实现类找(Ctrl+H)DefaultSqlSession类中查找他的commit无参数的方法,因为我们调用的也是他的无参方法,往下看
public void commit() { this.commit(false); } public void commit(boolean force) { try { this.executor.commit(this.isCommitOrRollbackRequired(force)); this.dirty = false; } catch (Exception var6) { throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + var6, var6); } finally { ErrorContext.instance().reset(); } }
看出来了吗?他调用了他下面的一个有参数的方法,并且传进去了参数,我们稍做记录,force是false
dirty变成false在执行器的提交下面,说明数据不是脏的了
executor.commit(XXX)执行器的提交,我们看里面的那个方法,commit在方法参数里面调用了方法并拿到返回值,看一下那个里面的方法,注意传进去的值为false
private boolean isCommitOrRollbackRequired(boolean force) { return !this.autoCommit && this.dirty || force; }
这个方法说实话,一眼看上去有点懵,但是你知道 ! && || 的使用优先级,你就可以计算出来了 &&>||>! 结果return回去的是true
上面的this.dirty是在上回说到的增删改都会底层调用update方法,里面改为true的
上面的autoCommit则创建sqlSession的时候就早早的改为false,OpenSession方法底层
接下来看上面的上面的executor.commit(XXX)方法,,他是Executor执行器接口的,找他的实现类BaseExecutor类的commit的带boolean参数的方法
public void commit(boolean required) throws SQLException { if(this.closed) { throw new ExecutorException("Cannot commit, transaction is already closed"); } else { this.clearLocalCache(); this.flushStatements(); if(required) { this.transaction.commit(); } } }
里面有一些不需要关注,不过简单提一嘴,clearLocalCache()清理缓存,flushStatements刷新参数
最终级的关注点就是因为if中的required是传进来的参数,上面已经解释过是true,所以他执行下一行代码就是transaction.commit(),哦,transaction的英文名就叫做事物
终结一句话:session.commit()最终调度到了事物的提交 ,this.transaction.commit()
3.mybatis中insert和delete底层实现的方式
先点进去看一下insert方法
用ctrl加鼠标左键点进去看
发现是一个接口SqlSession的方法,没有实现 ,但是通过里氏替换原则的想法,他是接口接收了实现类,所以找他的实现类DefaultSqlSession(idea快捷键ctrl+H)
进去后用ctrl+F查找insert,可以看到多个insert方法构成的重载,但是他们的方法实现调用了这个
public int insert(String statement) { return this.insert(statement, (Object)null); } public int insert(String statement, Object parameter) { return this.update(statement, parameter); }
上面那个方法调用了下面的方法,下面insert方法确实调用了update方法
再看一下delete的方法,
他和insert类似,都是接口SqlSession的方法,所以还是找他的实现类DefalutSqlSession
进去之后继续寻找delete方法
public int delete(String statement) { return this.update(statement, (Object)null); } public int delete(String statement, Object parameter) { return this.update(statement, parameter); }
可以看出他也是调用了update方法,所以就引发了好奇,update带俩个参数的那个方法中有什么?为什么都调用它呢?
看一下update方法
public int update(String statement, Object parameter) { int var4; try { this.dirty = true; MappedStatement e = this.configuration.getMappedStatement(statement); var4 = this.executor.update(e, this.wrapCollection(parameter)); } catch (Exception var8) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + var8, var8); } finally { ErrorContext.instance().reset(); } return var4; }
这里简单的提一下,
1)dirty=true;代表了他已经成为脏数据,意思就是内存上的数据和数据库中不一致,为什么这儿会改,或许就要谈论到为什么增删改需要手动提交事务(session.commit())和
session.close为什么会造成事物的回滚,可能下几章博客会有简单刨析一下
2)MappedStatement这个我找了一些资料,但是从百度搜索的没有告诉明白这个是什么东西,简单的说一下,如果错误,请大家帮我指出来,它应该是获取到mybatis配置中的具体SQL
3)var4就是用执行器执行sql,接收返回值,由update返回值是int,并且是var4,可以推断出他应该是返回受影响的行数
4)ErrorContext从字面意思理解为错误的上下文,instance是实例的意思,他源码是通过静态方法,拿到ErrorContext对象实例或创建实例并返回回来,单例的写法,reset是复位,重置,
初始化的意思