(1)测试文件要放在src/test/java的com.etc.test包中。
(2)要将 junit5 和 spring-test jar包的坐标加入pom.xml文件中。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.1.1.RELEASEversion>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiterartifactId>
<version>5.6.1version>
<scope>testscope>
dependency>
(3)使用Junit5完成单元测试,在类上添加一个@SpringJUnitConfig注解,在方法上增加一个@Test 注解,要注意大小写。Run as -> JUnit test 运行这个方法的时候,如果出现了绿色,表示单元测试成功,如果出现红色,则表示单元测试失败,程序代码有问题。
package com.etc.test;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.etc.dao.BlogMapper;
@SpringJUnitConfig(locations="classpath:applicationContext.xml")
public class TestBlogMapper {
@Autowired
private BlogMapper blogmapper;
@Test
public void testSelectBlog() {
System.out.println(blogmapper.selectBlog(1));
}
}
(1)在pom.xml文件中加入log4j的jar包坐标。
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
(2)添加 log4j.properties 配置文件。
# \u5168\u5C40\u65E5\u5FD7\u914D\u7F6E
log4j.rootLogger=ERROR, stdout
# MyBatis \u65E5\u5FD7\u914D\u7F6E
log4j.logger.com.etc.dao=TRACE
log4j.logger.com.etc.test=DEBUG
# \u63A7\u5236\u53F0\u8F93\u51FA
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
(3)在 src/main/resources 中加入一个mybatis-config.xml文件,并设置使用log4j日志。
<configuration>
<settings>
<setting name="logImpl" value="LOG4J"/>
settings>
configuration>
(4)修改applicationContext.xml文件,使其读取mybatis-config.xml中的配置。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml">property>
bean>
Druid首先是一个数据库连接池。Druid是目前最好的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。
Druid是一个JDBC组件,它包括三部分: (1)DruidDriver 代理Driver,能够提供基于Filter-Chain模式的插件体系。(2)DruidDataSource 高效可管理的数据库连接池。(3)SQLParser 。
(1)替换DBCP和C3P0。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。
(2)可以监控数据库访问性能,Druid内置提供了一个功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。
(3)数据库密码加密。直接把数据库密码写在配置文件中,这是不好的行为,容易导致安全问题。DruidDruiver和DruidDataSource都支持PasswordCallback。
(4)SQL执行日志,Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,你可以按需要选择相应的LogFilter,监控你应用的数据库访问情况。
(5)扩展JDBC,如果你要对JDBC层有编程的需求,可以通过Druid提供的Filter机制,很方便编写JDBC层的扩展插件。
(1)在Maven项目的pom.xml文件中加入druid连接池包。
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.0.12version>
dependency>
(2)Druid数据源配置:在applicationContext.xml中加入alibaba的Druid数据源。
<bean id="dataSource"
class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close">
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" />
<property name="driverClassName"
value="${jdbc_driverClassName}">property>
<property name="filters" value="${filters}" />
<property name="maxActive" value="${maxActive}" />
<property name="initialSize" value="${initialSize}" />
<property name="maxWait" value="${maxWait}" />
<property name="minIdle" value="${minIdle}" />
<property name="timeBetweenEvictionRunsMillis"
value="${timeBetweenEvictionRunsMillis}" />
<property name="minEvictableIdleTimeMillis"
value="${minEvictableIdleTimeMillis}" />
<property name="validationQuery" value="${validationQuery}" />
<property name="testWhileIdle" value="${testWhileIdle}" />
<property name="testOnBorrow" value="${testOnBorrow}" />
<property name="testOnReturn" value="${testOnReturn}" />
<property name="maxOpenPreparedStatements"
value="${maxOpenPreparedStatements}" />
<property name="removeAbandoned" value="${removeAbandoned}" />
<property name="removeAbandonedTimeout"
value="${removeAbandonedTimeout}" />
<property name="logAbandoned" value="${logAbandoned}" />
bean>
完整的applicationContext.xml以及db.properties
<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"
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/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<context:component-scan base-package="com.etc">context:component-scan>
<context:property-placeholder location="classpath:db.properties" />
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
bean>
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" />
<property name="driverClassName" value="${jdbc_driverClassName}">property>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.etc.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
bean>
beans>
jdbc_driverClassName=com.mysql.cj.jdbc.Driver
jdbc_url=jdbc:mysql://localhost:3306/bookdb?serverTimezone=Asia/Shanghai
jdbc_username=root
jdbc_password=root
filters=stat,wall
maxActive=20
initialSize=1
maxWait=60000
minIdle=10
maxIdle=15
timeBetweenEvictionRunsMillis=60000
minEvictableIdleTimeMillis=300000
validationQuery=SELECT 'x'
testWhileIdle=true
testOnBorrow=false
testOnReturn=false
maxOpenPreparedStatements=20
removeAbandoned=true
removeAbandonedTimeout=1800
logAbandoned=true
(3)配置Druid和Spring关联监控配置:在applicationContext.xml文件加入拦截器。在web.xml文件中启用Web监控统计功能。
<bean id="druid-stat-interceptor"
class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor">
bean>
<bean id="druid-stat-pointcut"
class="org.springframework.aop.support.JdkRegexpMethodPointcut"
scope="prototype">
<property name="patterns">
<list>
<value>com.etc.dao.*value>
list>
property>
bean>
<aop:config>
<aop:advisor advice-ref="druid-stat-interceptor"
pointcut-ref="druid-stat-pointcut" />
aop:config>
<filter>
<filter-name>DruidWebStatFilterfilter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilterfilter-class>
<init-param>
<param-name>exclusionsparam-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*param-value>
init-param>
<init-param>
<param-name>profileEnableparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>DruidWebStatFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<servlet>
<servlet-name>DruidStatViewservlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServletservlet-class>
<init-param>
<param-name>loginUsernameparam-name>
<param-value>rootparam-value>
init-param>
<init-param>
<param-name>loginPasswordparam-name>
<param-value>rootparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>DruidStatViewservlet-name>
<url-pattern>/druid/*url-pattern>
servlet-mapping>
(1)在appicationContext.xml的Namespaces(命名空间)中添加aop和tx。
(2)在appicationContext.xml中加入事务管理器和事务管理驱动。
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
<tx:annotation-driven
transaction-manager="transactionManager" />
(3)在service层的某个类或者方法上加上@Transactional注解。
Propagationkey属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部分是传播行为。有以下选项可供使用:
(1)PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
(2)PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
(3)PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
(4)PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
(5)PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
(6)PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_REQUIRED案例:
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
//转账功能
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void trans1(int flag) {
// 金额的转入
accountMapper.tansin(1, 5.0);
if (flag == 1) {
throw new RuntimeException("ex-trans1");
}
// 金额的转出(调用trans3方法)
trans2(2);
}
@Override
public void trans2(int flag) {
/// 金额的转出
if (flag == 2) {
throw new RuntimeException("ex-trans2");
}
accountMapper.tansout(2, 5.0);
}
}
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testtrans() {
accountService.trans1(0);
}
}
此时默认的隔离级别起了作用:PROPAGATION_REQUIRED,支持当前事务,如果没有,则新建事务,如果当前已经有事务,则合并为一个事务。trans1方法带有事务,trans1和trans2任何一个方法抛出异常,则都会回滚,即两个方法都受到事务管理。
(1)事务并发可能产生的问题如下:
脏读:A事务还未提交,B事务就读到了A操作的结果(破坏了隔离性)。
不可重复读:A事务在本次事务中,对自己未操作过数据,进行多次读取,结果出现不一致或记录不存在的情况(破坏了一致性,重点是update和delete)。
幻读:A事务在本次事务中,先读取了一遍数据,发现数据不存在,过了一会,又读取了一遍,发现又有数据了(破坏了一致性,重点是insert)。
(2)事务的四种隔离级别:
Read Uncommitted:保证了读取过程中不会读取到非法数据。
隔离级别在于处理多事务的并发问题。我们知道并行可以提高数据库的吞吐量和效率,但是并不是所有的并发事务都可以并发运行。
READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了脏读取。该级别适用于大多数系统。
REPEATABLE READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了脏读取和不可重复读取的情况,但是带来了更多的性能损失。
Serializable:最严格的级别,事务串行执行,资源消耗最大;可读,不可写。写数据必须等待另一个事务结束。
隔离级别默认为JDBC数据库的Level。
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
//转账功能
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public void trans1(int flag) {
// 金额的转入
accountMapper.tansin(1, 5.0);
if (flag == 1) {
throw new RuntimeException("ex-trans1");
}
// 金额的转出(调用trans3方法)
trans2(2);
}
@Override
public void trans2(int flag) {
/// 金额的转出
if (flag == 2) {
throw new RuntimeException("ex-trans2");
}
accountMapper.tansout(2, 5.0);
}
}
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testtrans() {
accountService.trans1(0);
}
}
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
//转账功能
@Override
@Transactional(timeout = 1000)
public void trans1() {
// 金额的转入
accountMapper.tansin(1, 5.0);
// 金额的转出
accountMapper.tansout(2, 5.0);
}
}
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testtrans() {
accountService.trans1();
}
}
事务又会分为读写事务和只读事务,只读事务并不是一个强制选项,它只是一个暗示,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。
从设置的时间点(时间点A)开始到事务结束的过程中,该事务将看不见其他事务所提交的数据,即查询中不会出现别人在时间点A之后提交的数据。
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
//转账功能
@Override
@Transactional(readOnly=true)
public void read() {
accountMapper.read();
}
}
Spring中的@Transactional(rollbackFor = Exception.class)事务处理,当你的方法中抛出Exception异常(rollbackFor属性指定的异常)时,它会将事务回滚到进入此方法前的状态,数据库中的数据将不会改变。
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
//转账功能
@Override
@Transactional(rollbackFor = RuntimeException.class)
public void trans1() {
// 金额的转入
accountMapper.tansin(1, 5.0);
// 金额的转出
accountMapper.tansout(2, 5.0);
}
}
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testtrans() {
accountService.trans1();
}
}