【SSM项目】尚筹网(二)基于Servlet3.0项目搭建:日志系统以及声明式事务
1 日志系统
常见的日志系统实现log4j、JUL(jdk自带)、log4j2、logback(和SLF4J同一个作者,能够天然衔接),这些实现就类似于java的接口实现,而SLF4J就类似于java的接口。如下图是slf4j对不同日志系统接口实现的整合。
1.1 导入依赖
<!-- 日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency>
1.2 主动打印日志
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RootConfig.class}) public class CrowdTest { @Test public void test() throws SQLException { // 1 获取logger对象,传入的class对象就是当前打印日志的类 Logger logger = LoggerFactory.getLogger(CrowdTest.class); // 2 根据当前的日志级别打印日志 logger.debug("123"); } }
17:48:31.771 [main] DEBUG com.hikaru.crowd.CrowdTest - 123
1.3 替换Spring的JCL
Spring5可以省略这一步,因为发现slf4j后会自动进行替换,这时候直接导入依赖使用logback就可以。
1.4 logback配置文件
想要控制日志格式以及级别需要进行配置logback.xml.
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="true"> <!-- 指定日志输出的位置 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!-- 日志输出的格式 --> <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体 内容、换行 --> <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n </pattern> </encoder> </appender> <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR --> <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 --> <root level="INFO"> <!-- 指定打印日志的 appender,这里通过“STDOUT”引用了前面配置的 appender --> <appender-ref ref="STDOUT"/> </root> <!-- 根据特殊需求指定局部日志级别 --> <logger name="com.hikaru.crowd.mapper" level="DEBUG"/> </configuration>
一般情况下是全局指定INFO级别,然后根据特殊需求指定局部日志级别
如这里就是对mapper使用了DEBUG级别,则会打印出mapper接口方法执行的SQL
[18:14:51.257] [DEBUG] [main] [com.hikaru.crowd.mapper.UserMapper.getMaxId] [==> Preparing: select MAX(id) from t_user] [18:14:51.289] [DEBUG] [main] [com.hikaru.crowd.mapper.UserMapper.getMaxId] [==> Parameters: ] [18:14:51.313] [DEBUG] [main] [com.hikaru.crowd.mapper.UserMapper.getMaxId] [<== Total: 1]
而且需要注意的一点是,这里的mybatis也能够使用日志的原因,是@Mapper和@MapperScan两个注解(在RootConfig核心配置类中)使得mapper接口接收了IOC容器的管理,当IOC容器使用logback的时候,自然mapper接口(准确来说是mapper接口对应的IOC容器中的具体动态实现,来源于mybatis核心工厂指定的mapper配置文件)也能够使用logback
/** * mybatis核心工厂 * @param dataSource * @return */ @Bean SqlSessionFactoryBean getSqlSessionFactoryBean(DruidDataSource dataSource) throws IOException { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); // 装配数据源 bean.setDataSource(dataSource); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); // 配置mapper映射文件地址,注意这里是getResource[s] bean.setMapperLocations(resolver.getResources("classpath:/mapper/*.xml")); return bean; }
如上是不通过xml配置核心配置工厂配置mapper接口对应的mapper文件
2 声明式事务
2.1 以前的做法
try{ // 关闭事务自动提交 connection.setAutoCommit(false); // 核心操作 Service. ... // 提交事务 connection.commit(); } catch(Exception e) { // 异常事务回滚 connection.rollBack(); } finally { // 关闭链接 connection.close(); }
在AOP中,
connection.setAutoCommit(false) 对应 前置通知
connection.commit() 对应 返回通知
connection.rollBack() 对应 异常通知
connection.close() 对应 后置通知
2.2 注解的做法
而在框架环境下,可以由spring来管理通用事务操作。
@Transactional public interface UserService { @Transactional public boolean addUser(User user); }
使用Transactional注解的类或者方法会自动变为上面的类型
2.3 xml(Servlet 2.0)的做法
思路
:使用SpringAOP来进行声明式事务的配置,首先要配置一个装配数据源的事务管理器txManager
,然后在通知txAdvice
中指定使用的事务管理器,并配置一些事务属性。而AOP包括通知和切点两部分,切点pointer
则可以通过切点表达式指定,然后通过aop:advisor
将切点和通知整合。
代码
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <!-- 配置自动扫描的包:主要是为了把Service扫描到IOC容器中 --> <context:component-scan base-package="com.hikaru.crowd.service"/> <!-- 开启生成代理对象 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 配置事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 装配数据源 --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置事务切面 --> <aop:config> <!-- 考虑到后面我们整合SpringSecurity,避免把UserDetailsService加入事务控制,让切入点表达式定位到ServiceImpl --> <aop:pointcut expression="execution(* *..*ServiceImpl.*(..))" id="txPointcut"/> <!-- 将切入点表达式和事务通知关联起来 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config> <!-- 配置事务通知 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <!-- 配置事务属性 --> <tx:attributes> <!-- 查询方法:配置只读属性,让数据库知道这是一个查询操作,能够进行一定优化 --> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="query*" read-only="true"/> <tx:method name="count*" read-only="true"/> <tx:method name="save*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/> <tx:method name="update*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/> <tx:method name="remove*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/> <tx:method name="batch*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/> </tx:attributes> </tx:advice> </beans>
2.4 servlet 3.0的做法
思路和xml配置是相同的,首先
① 在核心配置类RootConfig开启Aspect生成代理对象
@Configuration @ComponentScan(value = "com.hikaru.crowd", excludeFilters = { @ComponentScan.Filter(classes = {RestController.class}) }) @PropertySource(value = "classpath:jdbc.properties") @MapperScan(value = "com.hikaru.crowd.mapper") // 开启Aspect生成代理对象 @EnableAspectJAutoProxy(proxyTargetClass = true) public class RootConfig {
这里对应的xml:
<!-- 开启生成代理对象 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
② 配置事务管理器并注入数据源
/** * 事务管理器 * @param ds * @return */ @Bean DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource ds) { DataSourceTransactionManager dm = new DataSourceTransactionManager(); dm.setDataSource(ds); return dm; }
③ 创建切面并注入IOC容器
@Aspect @Component public class TxAdvisor { // 声明为切点 @Pointcut("execution(* com.hikaru.crowd.service.impl.*.*(..))") public void pointCut() {}; // 声明为前置通知 @Before("pointCut()") public void before() { Logger logger = LoggerFactory.getLogger(TxAdvisor.class); logger.info("this is a info"); } @AfterThrowing("pointCut()") public void afterThrowing() { Logger logger = LoggerFactory.getLogger(TxAdvisor.class); logger.error("this is a error"); } }
④ 测试
@Test public void test() { User user = new User(); user.setUserName("tod4"); user.setLoginName("tod4"); userService.addUser(user); }
[19:46:29.235] [INFO ] [main] [com.hikaru.crowd.config.TxAdvisor] [this is a info]
但是这里是存在问题的,在ServiceImpl里面有一句除0操作,但是不仅没有触发异常通知而且甚至addUser都能正常执行。。百思不得其解,这里AOP也需要去看书补习,Spring视频讲得实在是太少了,等后面填坑写一下纯注解版的声明式事务吧
一般情况声明式事务肯定是直接使用封装好的@Transcational了!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步