console.log('点个关注再走吧🍺');|

Tod4

园龄:2年11个月粉丝:21关注:0

【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了!

posted @   Tod4  阅读(64)  评论(0编辑  收藏  举报
   
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起