signed

QiShunwang

“诚信为本、客户至上”

Java学习笔记-Day78 Maven(二)

2021/3/21 0:53:20   来源:

Java学习笔记-Day78 Maven(二)

  • 一、Spring单元测试
  • 二、配置log4j日志
  • 三、Druid数据库连接池
    • 1、Druid的简介
    • 2、Druid的作用
    • 3、Druid的使用
  • 四、Spring整合MyBatis实现事务管理
    • 1、注解实现事务管理的步骤
    • 2、事务的传播特性(Propagation)
      • 2.1、传播特性简介
      • 2.2、传播特性案例
    • 3、事务的隔离级别(Isolation)
      • 3.1、隔离级别简介
      • 3.2、隔离级别案例
    • 4、事务超时(timeout)
    • 5、只读事务(readOnly)
    • 6、事务回滚


一、Spring单元测试


(1)测试文件要放在src/test/java的com.etc.test包中。

在这里插入图片描述
(2)要将 junit5 和 spring-test jar包的坐标加入pom.xml文件中。

		<!-- 添加spring-test包,方便进行单元测试 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>5.1.1.RELEASE</version>
		</dependency>
		<!-- 添加junit5包 -->
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter</artifactId>
			<version>5.6.1</version>
			<scope>test</scope>
		</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));
	}
}

二、配置log4j日志


(1)在pom.xml文件中加入log4j的jar包坐标。

		<dependency>
		    <groupId>log4j</groupId>
		    <artifactId>log4j</artifactId>
		    <version>1.2.17</version>
		</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日志。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <settings>
    <setting name="logImpl" value="LOG4J"/>
  </settings>
</configuration>

(4)修改applicationContext.xml文件,使其读取mybatis-config.xml中的配置。

	<!-- 配置sqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 实例化sqlSessionFactory时需要使用上述配置好的数据源以及SQL映射文件 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 读取mybatis-config.xml文件 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    </bean>

三、Druid数据库连接池

1、Druid的简介


Druid首先是一个数据库连接池。Druid是目前最好的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。

Druid是一个JDBC组件,它包括三部分: (1)DruidDriver 代理Driver,能够提供基于Filter-Chain模式的插件体系。(2)DruidDataSource 高效可管理的数据库连接池。(3)SQLParser 。

2、Druid的作用


(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层的扩展插件。

3、Druid的使用


(1)在Maven项目的pom.xml文件中加入druid连接池包。

<!-- 添加druid连接池包 -->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
	<version>1.0.12</version>
</dependency>

(2)Druid数据源配置:在applicationContext.xml中加入alibaba的Druid数据源。

    <!-- 配置数据源,使用的是alibaba的Druid(德鲁伊)数据源 -->
	<!-- DruidDataSource 数据库连接,连接池,监控 -->
	<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}" />
		<!-- 打开 removeAbandoned 功能 -->
		<property name="removeAbandoned" value="${removeAbandoned}" />
		<!-- 1800 秒,也就是 30 分钟 -->
		<property name="removeAbandonedTimeout"
			value="${removeAbandonedTimeout}" />
		<!-- 关闭 abanded 连接时输出错误日志 -->
		<property name="logAbandoned" value="${logAbandoned}" />
	</bean>

完整的applicationContext.xml以及db.properties

<?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"
	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>
	<!-- 读取db.properties中的数据库连接信息 -->
	<context:property-placeholder location="classpath:db.properties" />
	<!-- 配置sqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 实例化sqlSessionFactory时需要使用上述配置好的数据源以及SQL映射文件 -->
        <property name="dataSource" ref="dataSource" />
    </bean>
    
    <!-- 配置数据源,使用的是alibaba的Druid(德鲁伊)数据源 -->
    <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">
        <!-- 扫描com.etc.dao这个包以及它的子包下的所有映射接口类 -->
        <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

(3)配置Druid和Spring关联监控配置:在applicationContext.xml文件加入拦截器。在web.xml文件中启用Web监控统计功能。

  • 将以下代码加入applicationContext.xml
	<!-- 配置druid拦截器 begin -->
	<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>
	<!-- 配置druid拦截器 end -->
  • 将以下代码加入web.xml
	<!--连接池启用Web监控统计功能 begin -->
	<filter>
		<filter-name>DruidWebStatFilter</filter-name>
		<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
		<init-param>
			<param-name>exclusions</param-name>
			<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
		</init-param>
		<init-param>
			<param-name>profileEnable</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>DruidWebStatFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<servlet>
		<servlet-name>DruidStatView</servlet-name>
		<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
		<init-param>
			<!-- 用户名 -->
			<param-name>loginUsername</param-name>
			<param-value>root</param-value>
		</init-param>
		<init-param>
			<!-- 密码 -->
			<param-name>loginPassword</param-name>
			<param-value>root</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
		<servlet-name>DruidStatView</servlet-name>
		<url-pattern>/druid/*</url-pattern>
	</servlet-mapping>
	<!-- 连接池启用Web监控统计功能 end -->

四、Spring整合MyBatis实现事务管理

1、注解实现事务管理的步骤

(1)在appicationContext.xml的Namespaces(命名空间)中添加aop和tx。

(2)在appicationContext.xml中加入事务管理器和事务管理驱动。

	<!-- 配置事务管理 begin -->
	<!-- 事务管理器transactionManager -->
	<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<!-- 事务管理驱动-->
	<tx:annotation-driven
		transaction-manager="transactionManager" />
	<!-- 配置事务管理 end -->

(3)在service层的某个类或者方法上加上@Transactional注解。

2、事务的传播特性(Propagation)

2.1、传播特性简介


Propagationkey属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部分是传播行为。有以下选项可供使用:

(1)PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

(2)PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。

(3)PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。

(4)PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

(5)PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

(6)PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

2.2、传播特性案例


PROPAGATION_REQUIRED案例:

  • service业务层
@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);
	}
}
  • TestAccount测试类
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")
public class TestAccount {
	@Autowired
	private AccountService accountService;
	@Test
	public void testtrans() {
		accountService.trans1(0);
	}
}

此时默认的隔离级别起了作用:PROPAGATION_REQUIRED,支持当前事务,如果没有,则新建事务,如果当前已经有事务,则合并为一个事务。trans1方法带有事务,trans1和trans2任何一个方法抛出异常,则都会回滚,即两个方法都受到事务管理。

3、事务的隔离级别(Isolation)

3.1、隔离级别简介


(1)事务并发可能产生的问题如下:

  • 脏读:A事务还未提交,B事务就读到了A操作的结果(破坏了隔离性)。

  • 不可重复读:A事务在本次事务中,对自己未操作过数据,进行多次读取,结果出现不一致或记录不存在的情况(破坏了一致性,重点是update和delete)。

  • 幻读:A事务在本次事务中,先读取了一遍数据,发现数据不存在,过了一会,又读取了一遍,发现又有数据了(破坏了一致性,重点是insert)。

(2)事务的四种隔离级别:

  1. Read Uncommitted:保证了读取过程中不会读取到非法数据。
    隔离级别在于处理多事务的并发问题。我们知道并行可以提高数据库的吞吐量和效率,但是并不是所有的并发事务都可以并发运行。

  2. READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了脏读取。该级别适用于大多数系统。

  3. REPEATABLE READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了脏读取和不可重复读取的情况,但是带来了更多的性能损失。

  4. Serializable:最严格的级别,事务串行执行,资源消耗最大;可读,不可写。写数据必须等待另一个事务结束。

隔离级别默认为JDBC数据库的Level。

3.2、隔离级别案例

  • service业务层
@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);
	}
}
  • TestAccount测试类
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")
public class TestAccount {
	@Autowired
	private AccountService accountService;
	@Test
	public void testtrans() {
		accountService.trans1(0);
	}
}

4、事务超时(timeout)


所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。

  • service业务层
@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);
	}
}
  • TestAccount测试类
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")
public class TestAccount {
	@Autowired
	private AccountService accountService;
	@Test
	public void testtrans() {
		accountService.trans1();
	}
}

5、只读事务(readOnly)


事务又会分为读写事务和只读事务,只读事务并不是一个强制选项,它只是一个暗示,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。

从设置的时间点(时间点A)开始到事务结束的过程中,该事务将看不见其他事务所提交的数据,即查询中不会出现别人在时间点A之后提交的数据。

  • service业务层
@Service
public class AccountServiceImpl implements AccountService {
	@Autowired
	private AccountMapper accountMapper;
	//转账功能
	@Override
	@Transactional(readOnly=true)
	public void read() {
		accountMapper.read();
	}
}

6、事务回滚


Spring中的@Transactional(rollbackFor = Exception.class)事务处理,当你的方法中抛出Exception异常(rollbackFor属性指定的异常)时,它会将事务回滚到进入此方法前的状态,数据库中的数据将不会改变。

  • service业务层
@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);
	}
}
  • TestAccount测试类
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")
public class TestAccount {
	@Autowired
	private AccountService accountService;
	@Test
	public void testtrans() {
		accountService.trans1();
	}
}