1.整合日志
1.1概述
spring与日志框架进行整合,日志框架就可以在控制台中输出一些Spring运行过程中的一些重要的信息,便于了解运行过程,利于程序调试
如何整合日志框架
默认
spring1 2 3早期都是commons-logging.jar
spring5.x默认整合的日志框架 logback log4j2
我们也可以整和log4j日志框架
1.2整合log4j
1.导入log4j依赖
<!-- 日志框架 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- 让Spring框架支持log4j 干掉默认的logback和log4j2 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
2.引入log4j.properties配置文件
### 配置根
log4j.rootLogger = debug,console
#显示SQL语句的打印 这里指定dao接口所在的报名
log4j.logger.com.shwsh.simulate.dao=TRACE
### 日志输出到控制台显示
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
2.整合Mybatis
2.1Mybatis编码回顾
大致分为4个步骤
- 1、表 实体类
- 2、DAO接口
- 3、编写DAO接口对应的xxxMapper.xml文件
- 4、主配置文件中注册xxxMapper.xml文件并编写一些必要配置(数据源 类别名等)
- 5、API调用 ,获取SqlSessionFactory后获取SqlSession,然后创建DAO接口的代理对象调用方法完成操作
2.2Mybatis开发中存在的问题
在Mybatis开发的过程中,我们要编写Mybatis的主配置文件,mybatis-config.xml,在主配置文件里要编写一些关于别名 数据源 注册Mapper文件的配置,就以注册Mapper文件来说,一般的这些Mapper文件都在一个包中,只是Mapper文件名不一样而已,但是只要有一个Mapper文件我们就需要注册一个,这就很繁琐。
另外就是关于SqlSessionFactory以及SqlSession及DAO接口实现类的创建都会存在大量的代码冗余。
2.3Spring整合Mybatis的基本思路
使用Spring整合Mybatis的基本核心就是使用Spring提供的两个类
SqlSessionFactoryBean 实现了FactoryBean接口
- 这个类解决的就是关于原始开发中SqlSessionFactory的创建,我们知道,SqlSessionFactory的作用就是将Mybatis的主配置文件封装成一个Configuration对象,然后存到SqlSessionFactory中,后来为了创建SqlSessions对象。
- 主配置文件核心部分就是数据源 注册mapper等功能,我们可以通过Spring提供的SqlSessionFactoryBean 为其注入这些属性,最终完成SqlSessionFactory的创建。所以我们就不需要写Mybatis的主配置文件了。
- 我们核心的需要给SqlSessionFactoryBean对象注入
一个数据源 一个Mapper文件所在包的信息,还有实体类所在的包名信息(为了起别名)
MapperScannerConfigure
这个类解决的是我们创建SqlSession对象以及最终通过SqlSession对象为我们创建对应的DAO接口的实现类,所以说我们需要注入 上面的SqlSessionFactoryBean 以及 对应的dao接口所在包的信息
2.4整合Mybatis开发步骤
2.4.1导入Spring整合Mybatis依赖
<!--导入Spring整合Mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
<!--mybatis-spring 依赖需要spring-jdbc的依赖 所以也要引入这个依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<!--Druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!--Mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
2.4.2编写Spring配置文件
<?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: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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--连接池对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://ip:3306/xxx?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--创建SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入数据源信息-->
<property name="dataSource" ref="dataSource"></property>
<!--注入实体类所在包名信息-->
<property name="typeAliasesPackage" value="com.shwsh.entity"/>
<!--注入Mapper文件所在位置信息-->
<property name="mapperLocations" value="classpath:mybatis_conf/mapper/*Mapper.xml">
</property>
</bean>
<!--创建SqlSession对象最后创建DAO对象-->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入SqlSessionFactory对象 为了创建SqlSession-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--注入dao接口所在的包名信息,为了让SqlSession创建DAO接口的实现类对象-->
<property name="basePackage" value="com.shwsh.simulate.dao"></property>
</bean>
</beans>
2.4.3测试
public static void main(String[] args) {
//获取工厂
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//注意这里我们在配置文件中配置的MapperScannerConfigurer对象就是为了让Spring给我们创建出DAO接口的实现类,我们使用对应的Dao接口的首字母小写就能拿到对应的DAO接口的对象
UserDao userDao = (UserDao) context.getBean("userDao");
Boolean result = userDao.insert(new User(null, "ss", "sd"));
System.out.println(result);
//true 插入成功
}
2.5整合后关于事务的问题
我们经过2.4节的测试发现,经过Spring整合了Mybatis后,我们的操作可以直接进入数据库,也就是说事务现在是自动提交的,我们知道在Mybatis开发时,我们可以通过SqlSessionFactory在创建SqlSession时指定autocommit为true即设置自动提交,默认autocommit为false即不自动提交。我们知道,控制事务就要控制连接,我们引入了连接池之后连接就是连接池控制的,不管是druid c3p0 还是dbcp等,都是自动提交事务,但是我们在未来的开发中显然不能设置连接自动提交事务,一定要开启事务,所以说我们要事务交给Spring来管理,所以请看下一章 Spring处理事务。
3.Spring事务处理
3.1事务的特性
见MySQL笔记
3.2如何控制事务的本质
JDBC控制事务:
Connection.setAutoCommit(false) //开启事务
Connection.commit(); //提交事务
Connection.rollback(); //回滚
Mybatis控制事务:
是否开启事务在使用SqlSessionFactory创建SqlSession时即调用openSession时传入true表示自动提交,如果没有传入autocommit默认是开始事务
sqlSession.commit(); //提交事务
sqlSession.rollback(); //回滚
Mybatis底层也封装了Connection,即底层也是通过Connection控制事务
结论:控制事务的底层就是控制连接,即Connection对象!!!
3.3Spring处理事务分析
Spring是通过AOP的方式进行事务开发
编码步骤
1、原始对象
这里的原始对象就是我们的Service层 即一个个的xxxServiceImpl
2、额外功能
这里的额外功能就是事务处理 即在目标方法执行前开启事务,执行完后提交事务,如果出现了异常就回滚事务
-------------------------------------------------------------------
原来AOP开发我们需要实现MethodInteceptor接口实现方法,书写原始方法+额外功能,
这里的原始方法+额外功能Spring已经帮我们做好了,封装在DataSourceTransactionManager类里,核心的是我们需要为其注入DataSource对象,即需要控制连接。因为想要控制事务必须要控制连接或是连接池
3、切入点
原来的开发我们需要写切入点表达式,用来标注这些切入点,而在Spring处理事务时
我们只需要使用@Transactional注解标注需要加入事务的地方,可以加在一个类上或者一个方法上
1 如果加在类上,那么这个类的所有方法都会加入事务
2 如果加在方法上,那么这个方法会加入事务
4、组装切面
使用标签进行切入点与额外功能的组装,使用 transaction-manager属性找到额外功能 而切入点在这里不需要配置了,因为我们为需要加入事务的类或者方法加入了@Transactional注解,Spring会自动的扫描这些注解,自动的完成与额外功能的组装
<tx:annotation-driven transaction-manager=""/>
3.4Spring事务编码
3.4.1导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
3.4.2编码
1、原始对象
<bean id="userService" class="com.shwsh.simulate.service.UserServiceImpl">
<!--整合了MapperScannerConfigurer自动帮我们创建了DAO接口的实现类 这里直接引用-->
<property name="userDao" ref="userDao"></property>
</bean>
2、额外功能
Spring将事务的额外功能封装在DataSourceTransactionManager类里,而且控制事务必须要控制连接或者连接池,所以要将连接对象注入
<!--事务的额外功能-->
<bean id="transacationManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入连接 控制事务-->
<property name="dataSource" ref="dataSource"></property>
</bean>
3、切入点
加上@Transactional注解
@Transactional
@Override
public Boolean register(User user) {
Boolean insert = userDao.insert(user);
return insert;
}
4、组装切面
<!--组装事务的额外功能 切入点自动扫描@Transactional注解-->
<tx:annotation-driven transaction-manager="transacationManager"></tx:annotation-driven>
3.5完整配置文件
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">
<!--数据源(连接池)对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://8.131.95.64:3306/sunspring?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="2741404507"/>
</bean>
<!--创建SqlSessionFactory 为其注入数据源 包扫描 mapper文件扫描等信息,然后就可以不用书写Myabtis的主配置文件了-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="typeAliasesPackage" value="com.shwsh.simulate.entity"></property>
<property name="mapperLocations" value="classpath:mybatis_conf/mapper/*Mapper.xml"></property>
</bean>
<!--创建SqlSession以及DAO对象-->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<property name="basePackage" value="com.shwsh.simulate.dao"></property>
</bean>
<!--原始对象 为其注入DAO-->
<bean id="userService" class="com.shwsh.simulate.service.UserServiceImpl">
<!--整合了MapperScannerConfigurer自动帮我们创建了DAO接口的实现类,这里直接引用即可-->
<property name="userDao" ref="userDao"></property>
</bean>
<!--事务的额外功能-->
<bean id="transacationManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入连接 控制事务-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--组装切面 事务的额外功能 切入点自动扫描@Transactional注解-->
<tx:annotation-driven transaction-manager="transacationManager"></tx:annotation-driven>
</beans>
3.6关于组装切面代理的切换
<!--组装切面 也可以使用 proxy-target-class="true"进行动态代理的切换 默认是false即JDK动态代理 为true切换为Cglib动态代理-->
<tx:annotation-driven transaction-manager="transacationManager" proxy-target-class="true"/>
说明:Spring事务处理底层就是AOP
3.7基于标签的事务配置方式(事务开发第二种方式)
基于注解@Transactional的事务配置回顾
<!--原始类-->
<bean id="userService" class="com.shwsh.simulate.service.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<!--额外功能-->
<bean id="transacationManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--切入点-->
@Transactional(设置事务属性)
public Boolean register(User user) {
}
<!--整合切面 找到额外功能 并自动扫描@Transactional注解-->
<tx:annatation-driven transaction-manager = "transacationManager"/>
基于标签的配置方式
跟注解的开发步骤不同之处就在于切入点以及事务属性的配置
和 组装切面
这两步
<!--1、原始类-->
<bean id="userService" class="com.shwsh.simulate.service.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<!--2、额外功能-->
<bean id="transacationManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--3、配置事务属性以及额外功能-->
<tx:advice id="txAdvice" transacation-manager="dataSourceTransactionManager">
<tx:attributes>
<!--可以使用通配符的方式xxx*-->
<tx:method name="register" isoloation="",propagation=""></tx:method>
<tx:method name="login" .....></tx:method>
等效于
@Transactional(isolation=,propagation=,......)
public void register(){
}
</tx:attributes>
</tx:advice>
<!--4、组装切面 原始AOP组装法-->
<aop:config>
<!--切入点--> <!--这里一般都使用包切入点-->
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.service.UserServiceImpl.register(..))"></aop:pointcut>
<!--组装切入点+额外功能(第三步配置的)-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor>
</aop:config>
4.Spring的事务属性
4.1事务属性分类
- 隔离属性
- 传播属性
- 只读属性
- 超时属性
- 异常属性
4.2如何添加事务属性
我们上面在开发Spring事务时,在切入点步骤使用的是Spring为我们提供的@Transcational注解,加了这个注解的类或方法我们的额外功能就会添加上去。
我们添加事务属性就在@Transcational注解中设置值
注解中的每一个属性值都对应上面的事务属性
@Transactional(islocation=,propagation=,readOnly=,timeout=,rollbackFor,noRollbackFor=,)
4.3隔离属性(ISOLATION)及使用
具体参考MySQL初级笔记
4.3.1并发访问导致的问题
- 脏读 (读到了另外的事务未提交的数据)
- 不可重复读 (读到了其他事物已提交的数据)
- 幻读 (对整张表的数据进行了修改)
4.3.2解决问题-设置隔离级别
1.Read Committed 解决脏读 不能解决不可重复读与幻读
2.Repeatable Read 解决脏读 不可重复读 不能解决幻读 (行锁) --MySQL默认隔离级别
3.Serializable 解决脏读 不可重复读 幻读 (表锁)
4.3.3隔离级别的并发安全性及效率
并发安全
Read Committed <Repeatable Read<Serializable
运行效率
Read Committed >Repeatable Read>Serializable
4.4Spring设置隔离级别
都是在@Transactional注解的isolation属性设置
隔离级别设置为Read Committed
@Transactional(isolation=Isolation.READ_COMMITTED)
隔离级别设置为Repeatable Read
@Transactional(isolation=Isolation.REPEATABLE_READ)
隔离级别设置为Serializable
@Transactional(isolation=Isolation.SERIALIABLE)
Spring默认的是Isolation.DEFAULT,即使用的是不同的数据库的默认隔离级别
4.5开发中如何设置隔离级别
开发中使用Spring指定的Isolation.DEFAULT即可,即直接使用不同数据库的默认级别就行
4.4传播属性(Progation)
4.4.1概述
指的就是事务的嵌套 例如Service调用Service 即一个事务包含若干事务
出现的问题:一个事务中包含了很多小的事务,他们彼此影响,最终会导致整个事务丧失原子性
4.4.2传播属性的值及其用法
Spring默认的传播属性是REQUIRED
传播属性的值 | 外部不存在事务 | 外部存在事务 | 用法 | 备注 |
---|---|---|---|---|
REQUIRED | 开启新的事务 | 融合到外部事务中 | @Transactional(propagation = Propagation.REQUIRED) | 增删改方法 |
SUPPORTS | 不开启事务 | 融合到外部事务中 | @Transactional(propagation = Propagation.SUPPORTS) | 查询方法 |
REQUIRES_NEW | 开启新的事务 | 挂起外部事务,创建新的事务,新事物完成后再执行外部事物 | @Transactional(propagation = Propagation.REQUIRES_NEW) | 日志记录方法中 |
NOT_SUPPORTED | 不开启事务 | 挂起外部事务,也不开始新事物 | @Transactional(propagation = Propagation.NOT_SUPPORTED) | 及其不常用 |
NEVER | 不开启事务 | 抛出异常 | @Transactional(propagation = Propagation.NEVER) | 及其不常用 |
MANDATORY | 抛出异常 | 融合到外部事务中 | @Transactional(propagation = Propagation.MANDATORY) | 及其不常用 |
4.5只读属性(readOnly)
针对于只进行查询操作的业务方法可以加入只读属性,提高运行效率
不常用
设置readOnly=true开启readOnly 默认值是false
@Transactional(readOnly=true)
4.6超时属性
一个事务等待的最长时间,超出了最长等待时间就会抛出异常
当前事务访问数据时,有可能访问的数据被别的事务进行加锁的处理,那么此时本事物就必须等待
默认值-1 表示有对应的数据库指定
时间单位: 秒
@Transactional(timeout=2)
4.7异常属性
Spring在进行事务处理的过程中
默认
对于RuntimeException及其子类,采用的是回滚策略
对于Exception及其子类(除了RuntimeException及其子类),采用的是提交的策略
使用
rollbackFor={java.lang.Exception.class,...} //设置出现那些异常进行回滚处理
noRollbackFor={xxxException.class} //设置出现那些异常进行提交处理
@Transactional(rollbackFor={xxException.class,...},noRollbackFor={xxException.class...})
建议:
未来实战中,使用默认值即可
4.8事务常见配置总结
1.隔离属性 使用默认值
2.传播属性 增删改(Required默认值) 查询操作——>Supports
3.只读属性 增删改(false默认值) 查询操作——>true
4.超时属性 使用默认值 -1
5.异常属性 使用默认值
如果是增删改操作 直接使用 @Transactional注解即可,无需指定属性值
如果是查询操作 @Transactional(propagation=Propagation.SUPPORTS,readOnly=true)
5.整合SpringMVC
略
使用
rollbackFor={java.lang.Exception.class,…} //设置出现那些异常进行回滚处理
noRollbackFor={xxxException.class} //设置出现那些异常进行提交处理
@Transactional(rollbackFor={xxException.class,…},noRollbackFor={xxException.class…})
建议:
未来实战中,使用默认值即可
## 4.8事务常见配置总结
```markdown
1.隔离属性 使用默认值
2.传播属性 增删改(Required默认值) 查询操作——>Supports
3.只读属性 增删改(false默认值) 查询操作——>true
4.超时属性 使用默认值 -1
5.异常属性 使用默认值
如果是增删改操作 直接使用 @Transactional注解即可,无需指定属性值
如果是查询操作 @Transactional(propagation=Propagation.SUPPORTS,readOnly=true)
5.整合logback
5.1导入依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.logback-extensions</groupId>
<artifactId>logback-ext-spring</artifactId>
<version>0.1.4</version>
</dependency>
5.2配置文件(logback.xml)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG"> <!--或者info级别-->
<appender-ref ref="STDOUT" />
</root>
</configuration>