文章目录
JdbcTemplate概述
它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。spring 框架为我们提供了很多
的操作模板类。
操作关系型数据的:
JdbcTemplate
HibernateTemplate
操作 nosql 数据库的:
RedisTemplate
操作消息队列的:
JmsTemplate
JdbcTemplate在 spring-jdbc-5.0.2.RELEASE.jar中,我们在导包的时候,除了要导入这个jar包外,还需要导入一个 spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。
JdbcDaoSupport介绍
jdbc的一些基本操作就不过多介绍了,在web博客中已经写过,最主要的目的是介绍JdbcDaoSupport的原理。
1.不使用JdbcDaoSupport编写dao
测试
public class TestJdbcTemplate {
@Test
public void test() {
//获取容器
ClassPathXmlApplicationContext cpxac = new ClassPathXmlApplicationContext("bean.xml");
AccountDao accountDao = cpxac.getBean("accountDao", AccountDaoImpl.class);
Account account = accountDao.findAccountById(1);
System.out.println(account);
}
}
AccountDaoImpl2类
public class AccountDaoImpl2 implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Account findAccountById(Integer accountId) {
//定义sql
String sql = "select * from account where id = ?";
List<Account> accounts = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class), accountId);
if (accounts.isEmpty()) {
return null;
}
if (accounts.size() > 1) {
throw new RuntimeException("结果集不为1!");
}
return accounts.get(0);
}
public Account findAccountByName(String accountName) {
//定义sql
String sql = "select * from account where name = ?";
List<Account> accounts = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class), accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不为1!");
}
return accounts.get(0);
}
public List<Account> findAccountList() {
//定义sql
String sql = "select * from account";
List<Account> accounts =jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
return accounts;
}
}
2.使用JdbcDaoSupport编写dao和原理介绍
如果不使用JdbcDaoSupport,每个dao中就会注入JdbcTemplate这个重复代码,所以就使用JdbcDaoSupport将重复代码提取出来供程序员使用。
1.思考:两版Dao有什么区别呢?
答案:
第一种在Dao类中定义 JdbcTemplate 的方式,适用于所有配置方式(xml和注解都可以)。
因为这个代码是我自己写的当然可以加注解,或者xml配置方式。
第二种让Dao继承 JdbcDaoSupport 的方式,只能用于基于XML的方式,注解用不了。
这个使用的是spring中的JdbcDaoSupport,不可能在JdbcDaoSupport源码给jdbcTemplate属性加注解,所以所注解用不了。
//注入jdbcTemplate重复代码
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
测试
public class TestJdbcTemplate {
@Test
public void test() {
//获取容器
ClassPathXmlApplicationContext cpxac = new ClassPathXmlApplicationContext("bean.xml");
AccountDao accountDao = cpxac.getBean("accountDao", AccountDaoImpl.class);
Account account = accountDao.findAccountById(1);
System.out.println(account);
}
}
JdbcDaoSupprot原理介绍
public class JdbcDaoSupport {
private JdbcTemplate jdbcTemplate;
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//不用初始化成员变量,因为我只在set方法中使用dataSource,不在其他方法中使用。
//private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
//this.dataSource = dataSource;
if (jdbcTemplate == null) {
jdbcTemplate = createJdbcTemplate(dataSource);
}
}
private JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
AccountDaoImpl类继承JdbcDaoSupport
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
public Account findAccountById(Integer accountId) {
//定义sql
String sql = "select * from account where id = ?";
List<Account> accounts = getJdbcTemplate().query(sql, new BeanPropertyRowMapper<Account>(Account.class), accountId);
if (accounts.isEmpty()) {
return null;
}
if (accounts.size() > 1) {
throw new RuntimeException("结果集不为1!");
}
return accounts.get(0);
}
public Account findAccountByName(String accountName) {
//定义sql
String sql = "select * from account where name = ?";
List<Account> accounts = getJdbcTemplate().query(sql, new BeanPropertyRowMapper<Account>(Account.class), accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不为1!");
}
return accounts.get(0);
}
public List<Account> findAccountList() {
//定义sql
String sql = "select * from account";
List<Account> accounts = getJdbcTemplate().query(sql, new BeanPropertyRowMapper<Account>(Account.class));
return accounts;
}
}
spring配置文件bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置dao-->
<bean id="accountDao" class="com.ginger.dao.impl.AccountDaoImpl">
<!--
dao配置JdbcTemplate的JdbcDaoSupprot工具类方式一
-->
<!--<property name="jdbcTemplate" ref="jdbcTemplate"></property>-->
<!--
dao配置JdbcTemplatede的JdbcDaoSupprot工具类方式二
-->
<property name="dataSource" ref="driverManagerDataSource"></property>
</bean>
<!--配置JdbcDaoSupprot
其实这里不需要配置JdbcDaoSupprot,子类会帮忙初始化。
在这里开始出现了一个问题,我是把jdbcTemplate注入JdbcDaoSupport该类中,在dao使用时直接继承
进行测试,但是dao中继承的jdbcTemplate一直为null。其实仔细想想是不对的,子类已经继承jdbcTemplate
可以注入到AccountDaoImpl就可以了。
-->
<!--
<bean id="jdbcDaoSupport" class="com.ginger.dao.impl.JdbcDaoSupport">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
-->
<!--配置jdbctemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="driverManagerDataSource"></property>
</bean>
<!--配置内置数据源连接池-->
<!--spring 框架也提供了一个内置数据源,我们也可以使用 spring 的内置数据源,它就在spring-jdbc-5.0.2.REEASE.jar 包中:-->
<bean id="driverManagerDataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///travel"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
Spring中使用aop配置事务
1.bean.xml配置事务
测试代码
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",500f);
}
}
AccountServiceImpl类
/**
* 账户的业务层实现类
* 事务控制应该都是在业务层
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
@Override
public void deleteAccount(Integer acccountId) {
accountDao.deleteAccount(acccountId);
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney() - money);
//2.4转入账户加钱
target.setMoney(target.getMoney() + money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i = 1 / 0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
AccountDaoImpl类
/**
* 账户的持久层实现类
*/
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
private ConnectionUtils connectionUtils;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
@Override
public List<Account> findAllAccount() {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountById(Integer accountId) {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void saveAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void updateAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void deleteAccount(Integer accountId) {
try{
runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?",accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountByName(String accountName) {
try{
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return accounts.get(0);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
实体类
/**
* 账户的实体类
*/
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
工具类和通知
ConnectionUtils工具类
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try{
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
TransactionManager通知类
/**
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
spring配置文件bean.xml
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置注解扫描-->
<context:component-scan base-package="com.ginger"></context:component-scan>
<!--配置service-->
<bean id="accountService" class="com.ginger.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置dao-->
<bean id="accountDao" class="com.ginger.dao.impl.AccountDaoImpl">
<property name="runner" ref="queryRunner"></property>
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置dbutils的QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"></bean>
<!--配置ConnectionUtils-->
<bean id="connectionUtils" class="com.ginger.utlis.ConnectionUtils">
<property name="dataSource" ref="comboPooledDataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="com.ginger.utlis.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置c3p0连接池-->
<bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///travel"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--aop配置事务-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.ginger.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="txAdvice" ref="transactionManager">
<!--配置前置通知-->
<aop:before method="beginTransaction" pointcut-ref="pointcut"></aop:before>
<!--配置后置通知-->
<!--<aop:after-returning method="commit" pointcut-ref="pointcut"></aop:after-returning>-->
<!--配置异常通知-->
<aop:after-throwing method="rollback" pointcut-ref="pointcut"></aop:after-throwing>
<!--配置最终通知-->
<aop:after method="release" pointcut-ref="pointcut"></aop:after>
<aop:after method="commit" pointcut-ref="pointcut"></aop:after>
</aop:aspect>
</aop:config>
</beans>
pom.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ginger</groupId>
<artifactId>springtx</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
<dependencies>
<!--spring核心包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--spring织入包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--spring整合junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--c3p0连接池-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!--dbutils工具类-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
</project>
2.注解配置事务
- 使用注解配置通知(前置通知、后置通知、异常通知、最终通知),其实就是配置aop事务,这种注解配置方式代码执行顺序有问题,是spring本来就有的问题,这种方式不适合注解配置,只能使用bean.xml配置文件方式。
- 如果非要使用注解可以使用环绕通知注解配置,就不会有这个问题了。
下面使用注解配置环绕通知
测试类
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",500f);
}
}
AccountServiceImpl类
/**
* 账户的业务层实现类
* 事务控制应该都是在业务层
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Override
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
@Override
public void deleteAccount(Integer acccountId) {
accountDao.deleteAccount(acccountId);
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney() - money);
//2.4转入账户加钱
target.setMoney(target.getMoney() + money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i = 1 / 0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
AccountDaoImpl类
/**
* 账户的持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
@Autowired
private ConnectionUtils connectionUtils;
@Override
public List<Account> findAllAccount() {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountById(Integer accountId) {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void saveAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void updateAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void deleteAccount(Integer accountId) {
try{
runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?",accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountByName(String accountName) {
try{
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return accounts.get(0);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
工具类和通知
ConnectionUtils类
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
@Component("connectionUtils")
public class ConnectionUtils {
@Autowired
private DataSource dataSource;
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try{
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
TransactionManager通知类(注解配置环绕通知)
/**
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
@Component("transactionManager")
@Aspect//表示该类是一个切面类
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
//配置切入点
@Pointcut("execution(* com.ginger.service.impl.*.*(..))")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) {
try {
//前置通知
beginTransaction();
//目标方法参数
Object[] objs = pjp.getArgs();
//调用目标方法
Object obj = pjp.proceed(objs);
//后置通知
//commit();
return obj;
} catch (Throwable throwable) {
try {
//异常通知
rollback();
} catch (Exception e) {
e.printStackTrace();
}
throw new RuntimeException(throwable);
//throwable.printStackTrace();
} finally {
//后置通知
commit();
//最终通知
release();
}
}
/**
* 开启事务
*/
//@Before("pointcut()")//前置通知
public void beginTransaction() {
System.out.println("前置通知");
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 提交事务
*/
//@AfterReturning("pointcut()")//后置通知
public void commit() {
System.out.println("后置通知");
try {
connectionUtils.getThreadConnection().commit();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 回滚事务
*/
//@AfterThrowing("pointcut()")//异常通知
public void rollback() {
System.out.println("异常通知");
try {
connectionUtils.getThreadConnection().rollback();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 释放连接
*/
//@After("pointcut()")//最终通知
public void release() {
System.out.println("最终通知");
try {
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
} catch (Exception e) {
e.printStackTrace();
}
}
}
spring配置文件bean.xml
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置注解扫描-->
<context:component-scan base-package="com.ginger"></context:component-scan>
<!--配置QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"></bean>
<!--配置c3p0连接池-->
<bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///travel"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
Spring中内置事务
1.bean.xml配置事务
测试代码
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
AccountServiceImpl类
/**
* 账户的业务层实现类
*
* 事务控制应该都是在业务层
*/
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
AccountDaoImpl类
/**
* 账户的持久层实现类
*/
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
实体类
/**
* 账户的实体类
*/
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
1.事务传播行为图解
这个图是我自己后来加上的,更加理解事务传播行为。
spring配置文件bean.xml
<?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: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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置service-->
<bean id="accountService" class="com.ginger.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置dao-->
<bean id="accountDao" class="com.ginger.dao.impl.AccountDaoImpl">
<!--第一种配置方式-->
<!--<property name="jdbcTemplate" ref="jdbcTemplate"></property>-->
<!--第二种配置方式,更方便。不用文件中配置JdbcTemplate,会自动创建一个。-->
<property name="dataSource" ref="driverManagerDataSource"></property>
</bean>
<!--配置jdbcTemp-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="driverManagerDataSource"></property>
</bean>
<!--配置spring数据库连接池-->
<bean id="driverManagerDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///travel"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置spring事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="driverManagerDataSource"></property>
</bean>
<!--配置spring中内置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
-->
<tx:attributes>
<!--第一个兜不住第二兜-->
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.ginger.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
</beans>
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ginger</groupId>
<artifactId>springtx</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
<dependencies>
<!--spring核心包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--spring织入包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--springjdbc包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--spring事务包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--spring整合junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--dbutils工具类-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
</project>
2.注解配置事务
测试代码
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
AccountServiceImpl 类
/**
* 账户的业务层实现类
*
* 事务控制应该都是在业务层
*/
@Service
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
//需要的是读写型事务配置
@Transactional(propagation= Propagation.REQUIRED,readOnly=false)
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
AccountDaoImpl类
/**
* 账户的持久层实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
return accounts.isEmpty() ? null : accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
if (accounts.isEmpty()) {
return null;
}
if (accounts.size() > 1) {
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
}
}
spring的bean.xml配置文件
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启spring注解扫描-->
<context:component-scan base-package="com.ginger"></context:component-scan>
<!--配置jdbcTemp-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="driverManagerDataSource"></property>
</bean>
<!--配置spring数据库连接池-->
<bean id="driverManagerDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///travel"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置事务核心管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="driverManagerDataSource"></property>
</bean>
<!--开启spring注解支持事务-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
</beans>
Spring纯注解配置(取消bean.xml配置)
测试代码
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
使用java类配置spring的bean.xml文件
SpringConfiguration类
/**
* spring的配置类,相当于bean.xml
*/
@Configuration//表示该类是一个spring核心配置类
@ComponentScan("com.itheima")//扫描指定包下的注解
@Import({JdbcConfig.class,TransactionConfig.class})//导入Java类作为配置类
@PropertySource("jdbcConfig.properties")//指定classpath下数据库连接配置文件
@EnableTransactionManagement//表示开启spring注解配置事务
public class SpringConfiguration {
}
JdbcConfig类
/**
* 和连接数据库相关的配置类
*/
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建JdbcTemplate
* @param dataSource
* @return
*/
@Bean(name="jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
TransactionConfig类
/**
* 和事务相关的配置类
*/
public class TransactionConfig {
/**
* 用于创建事务管理器对象
* @param dataSource
* @return
*/
@Bean(name="transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
jdbcConfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///travel
jdbc.username=root
jdbc.password=1234
AccountServiceImpl类
/**
* 账户的业务层实现类
*
* 事务控制应该都是在业务层
*/
@Service("accountService")
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)//只读型事务的配置
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
//需要的是读写型事务配置
@Transactional(propagation= Propagation.REQUIRED,readOnly=false)
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
//int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
AccountDaoImpl类
/**
* 账户的持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
实体类
/**
* 账户的实体类
*/
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>day04_eesy_07anno_tx_withoutxml</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
Spring5 的新特性
1.jdk 版本要求
spring5.0 在 2017 年 9 月发布了它的 GA(通用)版本。该版本是基于 jdk8 编写的,所以 jdk8 以下版本将无法使用。同时,可以兼容 jdk9 版本。
tomcat 版本要求 8.5 及以上。
注:
我们使用 jdk8 构建工程,可以降版编译。但是不能使用 jdk8 以下版本构建工程。
由于 jdk 和 tomcat 版本的更新,我们的 IDE 也需要同时更新。(目前使用的 eclipse 4.7.2)
2.利用jdk8版本更新的内容
第一:基于 JDK8 的反射增强
请看下面的代码:
public class Test {
//循环次数定义:10亿次
private static final int loopCnt = 1000 * 1000 * 1000;
public static void main(String[] args) throws Exception {
//输出jdk的版本
System.out.println("java.version=" + System.getProperty("java.version"));
t1();
t2();
t3();
}
// 每次重新生成对象
public static void t1() {
long s = System.currentTimeMillis();
for (int i = 0; i < loopCnt; i++) {
Person p = new Person();
p.setAge(31);
}
long e = System.currentTimeMillis();
System.out.println("循环10亿次创建对象的时间:" + (e - s));
}
// 同一个对象
public static void t2() {
long s = System.currentTimeMillis();
Person p = new Person();
for (int i = 0; i < loopCnt; i++) {
p.setAge(32);
}
long e = System.currentTimeMillis();
System.out.println("循环10亿次给同一对象赋值的时间: " + (e - s));
}
//使用反射创建对象
public static void t3() throws Exception {
long s = System.currentTimeMillis();
Class<Person> c = Person.class;
Person p = c.newInstance();
Method m = c.getMethod("setAge", Integer.class);
for (int i = 0; i < loopCnt; i++) {
m.invoke(p, 33);
}
long e = System.currentTimeMillis();
System.out.println("循环10亿次反射创建对象的时间:" + (e - s));
}
static class Person {
private int age = 20;
public int getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
}
修改JDK8位7测试
JDK8和JDK7的对比
第二:@NonNull 注解和@Nullable 注解的使用
用 @Nullable 和 @NotNull 注解来显示表明可为空的参数和以及返回值。这样就够在编译的时候处理空值而不是在运行时抛出 NullPointerExceptions。
第三:日志记录方面
Spring Framework 5.0 带来了 Commons Logging 桥接模块的封装, 它被叫做 spring-jcl 而不是标准的 Commons Logging。当然,无需任何额外的桥接,新版本也会对 Log4j 2.x, SLF4J, JUL ( java.util.logging) 进行自动检测。
3.核心容器的更新
Spring Framework 5.0 现在支持候选组件索引作为类路径扫描的替代方案。该功能已经在类路径扫描器中添加,以简化添加候选组件标识的步骤。
应用程序构建任务可以定义当前项目自己的 META-INF/spring.components 文件。在编译时,源模型是自包含的,JPA 实体和 Spring 组件是已被标记的。
从索引读取实体而不是扫描类路径对于小于 200 个类的小型项目是没有明显差异。但对大型项目影响较大。加载组件索引开销更低。因此,随着类数的增加,索引读取的启动时间将保持不变。
加载组件索引的耗费是廉价的。因此当类的数量不断增长,加上构建索引的启动时间仍然可以维持一个常数, 不过对于组件扫描而言,启动时间则会有明显的增长。
这个对于我们处于大型 Spring 项目的开发者所意味着的,是应用程序的启动时间将被大大缩减。虽然 20 或者 30 秒钟看似没什么,但如果每天要这样登上好几百次,加起来就够你受的了。使用了组件索引的话,就能帮助你每天过的更加高效。
你可以在 Spring 的 Jira 上了解更多关于组件索引的相关信息。
4.JetBrains Kotlin 语言支持
Kolin概述:是一种支持函数式编程编程风格的面向对象语言。Kotlin 运行在 JVM 之上,但运行环境并不限于 JVM。
5.响应式编程风格
此次 Spring 发行版本的一个激动人心的特性就是新的响应式堆栈 WEB 框架。这个堆栈完全的响应式且非阻塞,适合于事件循环风格的处理,可以进行少量线程的扩展。
Reactive Streams 是来自于 Netflix, Pivotal, Typesafe, Red Hat, Oracle, Twitter 以及Spray.io 的工程师特地开发的一个 API。它为响应式编程实现的实现提供一个公共的 API,好实现Hibernate 的 JPA。这里 JPA 就是这个 API, 而 Hibernate 就是实现。
Reactive Streams API 是 Java 9 的官方版本的一部分。在 Java 8 中, 你会需要专门引入依赖来使用 Reactive Streams API。
Spring Framework 5.0 对于流式处理的支持依赖于 Project Reactor 来构建, 其专门实现了Reactive Streams API。
Spring Framework 5.0 拥有一个新的 spring-webflux 模块,支持响应式 HTTP 和 WebSocket 客户端。Spring Framework 5.0 还提供了对于运行于服务器之上,包含了 REST, HTML, 以及 WebSocket 风格交互的响应式网页应用程序的支持。
在 spring-webflux 中包含了两种独立的服务端编程模型:
基于注解:使用到了@Controller 以及 Spring MVC 的其它一些注解;
使用 Java 8 lambda 表达式的函数式风格的路由和处理。
有 了 Spring Webflux, 你现在可以创建出 WebClient, 它是响应式且非阻塞的,可以作为RestTemplate 的一个替代方案。
这里有一个使用 Spring 5.0 的 REST 端点的 WebClient 实现:
WebClient webClient = WebClient.create();
Mono person = webClient.get()
.uri("http://localhost:8080/movie/42")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.then(response -> response.bodyToMono(Movie.class));
6.Junit5 支持
完全支持 JUnit 5 Jupiter,所以可以使用 JUnit 5 来编写测试以及扩展。此外还提供了一个编程以及扩展模型,Jupiter 子项目提供了一个测试引擎来在 Spring 上运行基于 Jupiter 的测试。
另外,Spring Framework 5 还提供了在 Spring TestContext Framework 中进行并行测试的扩展。
针对响应式编程模型, spring-test 现在还引入了支持 Spring WebFlux 的 WebTestClient 集成测试的支持,类似于 MockMvc,并不需要一个运行着的服务端。使用一个模拟的请求或者响应, WebTestClient 就可以直接绑定到 WebFlux 服务端设施。
你可以在这里找到这个激动人心的 TestContext 框架所带来的增强功能的完整列表。
当然, Spring Framework 5.0 仍然支持我们的老朋友 JUnit! 在我写这篇文章的时候, JUnit 5 还只是发展到了 GA 版本。对于 JUnit4, Spring Framework 在未来还是要支持一段时间的。
7.依赖类库的更新
终止支持的类库
Portlet.
Velocity.
JasperReports.
XMLBeans.
JDO.
Guava.
支持的类库
Jackson 2.6+
EhCache 2.10+ / 3.0 GA
Hibernate 5.0+
JDBC 4.0+
XmlUnit 2.x+
OkHttp 3.x+
Netty 4.1+