【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 @ 2022-09-23 19:14  Tod4  阅读(52)  评论(0编辑  收藏  举报