04-Spring、JbbcTemplate、AOP事务控制、事务控制
一、Spring中的JdbcTemplate
1.1、JdbcTemplate概述
是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。spring 框架为我们提供了很多的操作模板类。
操作关系型数据的:
JdbcTemplate
HibernateTemplate
操作 nosql 数据库的:
RedisTemplate
操作消息队列的:
JmsTemplate
需要用到的jar包:
spring-jdbc-5.0.2.RELEASE.jar
spring-tx-5.0.2.RELEASE.jar(和事务相关的)
1.2、JdbcTemplate对象的创建
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { public JdbcTemplate() { } public JdbcTemplate(DataSource dataSource) { this.setDataSource(dataSource); this.afterPropertiesSet(); } public JdbcTemplate(DataSource dataSource, boolean lazyInit) { this.setDataSource(dataSource); this.setLazyInit(lazyInit); this.afterPropertiesSet(); } public JdbcTemplate(DataSource dataSource, boolean lazyInit) { this.setDataSource(dataSource); this.setLazyInit(lazyInit); this.afterPropertiesSet(); } } public abstract class JdbcAccessor implements InitializingBean { public void setDataSource(@Nullable DataSource dataSource) { this.dataSource = dataSource; } }
除了默认的构造方法,都需要提供一个数据源。存在set方法(setDataSource(dataSource);)。可以通过依赖注入,在配置文件中进行配置。
1.3、spring中配置数据源
1.3.1、环境搭建
1.3.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
1.3.3、配置数据源
1.3.3.1、配置C3P0数据源
1.3.3.2、配置DBCP数据源
1.3.3.3、配置spring内置数据源
spring 框架也提供了一个内置数据源,可以使用 spring 的内置数据源,在 spring-jdbc-5.0.2.REEASE.jar 包中。
-->即类:org.springframework.jdbc.datasource.DriverManagerDataSource
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/eesy_spring"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>
1.3.4、将数据库连接的信息配置到属性文件中--->jdbcConfig.properties
【定义属性文件】 --->注意:此文件中不能带有空格,否则不能正确解析
jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///spring_day02 jdbc.username=root jdbc.password=root
【引入外部的属性文件】
方式一:
<!-- 引入外部属性文件: --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties"/> </bean>
方式二:
<context:property-placeholder location="classpath:jdbc.properties"/>
1.5、在dao中使用JdbcTemplate
1.5.2、方式一:在dao中定义jdbcTemplate
这种方式有什么问题?
就是当我们的 dao 有很多时,每个dao都有一些重复性的代码。如下:
private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; }
1.5.3、方式二:让dao继承JdbcDaoSupport
JdbcDaoSupport 是spring 框架为我们提供的一个类,该类中定义了一个 JdbcTemplate 对象,我们可以直接获取使用,但是要想创建该对象,需要为其提供一个数据源:
public abstract class JdbcDaoSupport extends DaoSupport { @Nullable private JdbcTemplate jdbcTemplate; /** * Set the JDBC DataSource to be used by this DAO. */ public final void setDataSource(DataSource dataSource) { if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) { this.jdbcTemplate = createJdbcTemplate(dataSource); initTemplateConfig(); } } /** * Create a JdbcTemplate for the given DataSource. * Only invoked if populating the DAO with a DataSource reference! * <p>Can be overridden in subclasses to provide a JdbcTemplate instance * with different configuration, or a custom JdbcTemplate subclass. * @param dataSource the JDBC DataSource to create a JdbcTemplate for * @return the new JdbcTemplate instance * @see #setDataSource */ protected JdbcTemplate createJdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } /** * Return the JDBC DataSource used by this DAO. */ @Nullable public final DataSource getDataSource() { return (this.jdbcTemplate != null ? this.jdbcTemplate.getDataSource() : null); } /** * Set the JdbcTemplate for this DAO explicitly, * as an alternative to specifying a DataSource. */ public final void setJdbcTemplate(@Nullable JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; initTemplateConfig(); } /** * Return the JdbcTemplate for this DAO, * pre-initialized with the DataSource or set explicitly. */ @Nullable public final JdbcTemplate getJdbcTemplate() { return this.jdbcTemplate; } /** * Initialize the template-based configuration of this DAO. * Called after a new JdbcTemplate has been set, either directly * or through a DataSource. * <p>This implementation is empty. Subclasses may override this * to configure further objects based on the JdbcTemplate. * @see #getJdbcTemplate() */ protected void initTemplateConfig() { } @Override protected void checkDaoConfig() { if (this.jdbcTemplate == null) { throw new IllegalArgumentException("'dataSource' or 'jdbcTemplate' is required"); } } /** * Return the SQLExceptionTranslator of this DAO's JdbcTemplate, * for translating SQLExceptions in custom JDBC access code. * @see org.springframework.jdbc.core.JdbcTemplate#getExceptionTranslator() */ protected final SQLExceptionTranslator getExceptionTranslator() { JdbcTemplate jdbcTemplate = getJdbcTemplate(); Assert.state(jdbcTemplate != null, "No JdbcTemplate set"); return jdbcTemplate.getExceptionTranslator(); } /** * Get a JDBC Connection, either from the current transaction or a new one. * @return the JDBC Connection * @throws CannotGetJdbcConnectionException if the attempt to get a Connection failed * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection(javax.sql.DataSource) */ protected final Connection getConnection() throws CannotGetJdbcConnectionException { DataSource dataSource = getDataSource(); Assert.state(dataSource != null, "No DataSource set"); return DataSourceUtils.getConnection(dataSource); } /** * Close the given JDBC Connection, created via this DAO's DataSource, * if it isn't bound to the thread. * @param con Connection to close * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection */ protected final void releaseConnection(Connection con) { DataSourceUtils.releaseConnection(con, getDataSource()); } }
以下是自己实现的代码:
public class JdbcDaoSupport { private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public JdbcTemplate getTemplate() { return jdbcTemplate; } // private DataSource dataSource; public void setDataSource(DataSource dataSource) { // this.dataSource = dataSource; if(this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()){ jdbcTemplate = createTemplate(dataSource); } } private JdbcTemplate createTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } }
账户持久层实现类,继承JdbcDaoSupport
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { /*private JdbcTemplate template; public void setTemplate(JdbcTemplate template) { this.template = template; } 需要优化的部分 */ public Account findAccountById(Integer accountId) { List<Account> accounts = super.getTemplate().query("SELECT * FROM account WHERE id=?", new BeanPropertyRowMapper<Account>(Account.class),accountId); return accounts.size()==0?null:accounts.get(0); } public Account findAccountByName(String accountName) { List<Account> accounts = super.getTemplate().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); } public void updateAccount(Account account) { super.getTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId()); } }
<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" 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.xsd"> <context:property-placeholder location="classpath:jdbcConfig.properties"></context:property-placeholder> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <!-- <property name="template" ref="jdbcTemplate"></property>--> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置jdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置数据源--> <!-- <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/eesy_spring"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> </beans>
上述两种Dao实现方式的区别
第一种:在 Dao 类中定义 JdbcTemplate 的方式,适用于所有配置方式(xml和注解都可以)。
第二种:让 Dao 继承 JdbcDaoSupport 的方式,只能用于基于 XML 的方式,注解用不了。
1、基于xml的AOP事务控制
<?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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--配置BeanFactory--> <!-- <bean id="beanFactory" class="com.itheima.factory.BeanFactory"> <property name="accountService" ref="accountService"></property> <property name="txManager" ref="txManager"></property> </bean>--> <!--配置代理AccountService--> <!--<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>--> <!--==============================================--> <!-- 配置Service --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean> <!--配置Dao对象--> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--注入数据源--> <constructor-arg name="ds" ref="dataSource"></constructor-arg> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy_spring"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!--配置ConnectionUtils工具类ConnectionUtils--> <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils"> <!--注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事务管理器--> <bean id="txManager" class="com.itheima.utils.TransactionManager"> <!--注入ConnectionUtils--> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置aop--> <aop:config> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <aop:aspect id="txAdvice" ref="txManager"> <!--配置前置通知:开始事务--> <aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before> <!--配置后置通知:开始事务--> <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning> <!--配置异常通知:回滚事务--> <aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing> <!--配置最终通知:释放连接--> <aop:after method="release" pointcut-ref="pt1"></aop:after> </aop:aspect> </aop:config> </beans>
/** * 和事务相关的工具类。包含了:开启事务,提交事务,关闭事务,回滚事务 */ public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 开启事务 */ public void beginTransaction(){ try { System.out.println("开始事务---------"); connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); System.out.println("回滚事务-----------"); }catch (Exception e){ e.printStackTrace(); } } /** * 释放链接 */ public void release(){ try { connectionUtils.getThreadConnection().close(); //还回连接池中 connectionUtils.removeConnection(); } catch (SQLException e) { e.printStackTrace(); } } }
/** * 账户的业务层实现类 */ public class AccountServiceImpl implements IAccountService{ private IAccountDao accountDao; /* private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; }*/ 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...."); Account sourceAccount = accountDao.findAccountByName(sourceName); Account targetAccount = accountDao.findAccountByName(targetName); sourceAccount.setMoney(sourceAccount.getMoney()-money); targetAccount.setMoney(targetAccount.getMoney()+money); accountDao.updateAccount(sourceAccount); // int i= 1/0; accountDao.updateAccount(targetAccount); } }
/** * 账户的持久层实现类 */ 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 ConnectionUtils { private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /**获取当前线程上的链接 * @return */ public Connection getThreadConnection(){ try { Connection conn = tl.get(); if(conn==null){ conn = dataSource.getConnection(); tl.set(conn); } //返回当前线程上的线程 return conn; } catch (Exception e) { throw new RuntimeException(); } } //将连接和线程解绑 public void removeConnection(){ tl.remove(); } }
2、基于注解的AOP实现事务控制及问题
执行顺序存在问题。-->会先调用最终通知,再调用后置通知(???可能spring框架自身的问题)。调用最终通知后,connection已经关闭了,在调用后再通知的时候就不进行提交。
解决:使用环绕通知
<?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"> <!--配置spring创建容器时要扫描的包--> <context:component-scan base-package="com.itheima" ></context:component-scan> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy_spring"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!--开启spring对于注解AOP的支持--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
@Component("txManager")
@Aspect
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){}
/**
* 开启事务
*/
// @Before("pt1()")
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
// @AfterReturning("pt1()")
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 回滚事务
*/
// @AfterThrowing("pt1()")
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放链接
*/
// @After("pt1()")
public void release(){
try {
connectionUtils.getThreadConnection().close();
connectionUtils.removeConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Around("pt1()")
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
//1.获取参数
Object[] args = pjp.getArgs();
//2.开始事务
this.beginTransaction();
//3.执行方法
pjp.proceed(args);
//4.提交事务
this.commit();
return rtValue;
} catch (Throwable throwable) {
//回滚事务
this.rollback();
throw new RuntimeException(throwable);
}finally {
//释放连接
this.release();
}
}
}
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
/* private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}*/
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) {
Account sourceAccount = accountDao.findAccountByName(sourceName);
Account targetAccount = accountDao.findAccountByName(targetName);
sourceAccount.setMoney(sourceAccount.getMoney()-money);
targetAccount.setMoney(targetAccount.getMoney()+money);
accountDao.updateAccount(sourceAccount);
// int i= 1/0;
accountDao.updateAccount(targetAccount);
}
}
@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);
}
}
}
@Component("ConnectionUtils")
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
/**获取当前线程上的链接
* @return
*/
public Connection getThreadConnection(){
try {
Connection conn = tl.get();
if(conn==null){
conn = dataSource.getConnection();
tl.set(conn);
}
//返回当前线程上的线程
return conn;
} catch (Exception e) {
throw new RuntimeException();
}
}
//将连接和线程解绑
public void removeConnection(){
tl.remove();
}
}
二、Spring中的事务控制
2.1、Spring事务控制
第一:JavaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理解决方 案。
第二:spring 框架为我们提供了一组事务控制的接口。这组接口是在 spring-tx-5.0.2.RELEASE.jar 中。
第三:spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现。
重点是使用配置的方式实现。
2.2、Spring中事务控制的API介绍
2.2.1、PlatformTransactionManager
此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法,如下图
在开发中都是使用其实现类。
真正管理事务的对象:
org.springframework.jdbc.datasource.DataSourceTransactionManager
使用 Spring JDBC 或 iBatis 进行持久化数据时使用
org.springframework.orm.hibernate5.HibernateTransactionManager
使用 Hibernate 版本进行持久化数据时使用
2.2.2、TransactionDefinition
事务的定义信息对象。
spring默认使用的是数据库的隔离级别。
-->传播行为:什么情况下必须有事务,什么情况可以不需要。
--->超时时间,可以配置使其永远不过期。
--->是否只读。一般查询方法采用只读。
2.2.2.1、事务的隔离级别
2.2.2.2、事务的传播行为
REQUIRED:
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值) -->最终都有。增删改的选择。
SUPPORTS:
支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务) --->只有查询才能用
MANDATORY:
使用当前的事务,如果当前没有事务,就抛出异常 。
REQUERS_NEW:
新建事务,如果当前在事务中,把当前事务挂起。
NOT_SUPPORTED:
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 。
NEVER:
以非事务方式运行,如果当前存在事务,抛出异常 NESTED。如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
2.2.2.3、超过时间
默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
2.2.2.4、是否是只读事务
建议查询时设置为只读。 --->如果不是查询设置为 读写 。
2.2.3、TransactionStatus
此接口提供的是事务具体的运行状态,方法介绍如下:
--->存储点可理解为事务是按步提交。一旦设置了存储点,每个存储点都是事务的一步。执行成功,提交这一步。所有步骤成功,整个事务结束。如果某一步没有成功,回滚到失败步,而不是回滚到重头开始。
2.3、基于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>
2.3.1.2、创建spring的配置文件并导入约束
此处需要导入aop和tx两个名称空间
<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/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">
2.3.1.3、准备数据库表和实体类
2.3.1.4:编写业务接口和实现类|
2.3.1.5:编写Dao接口和实现类
2.3.1.6:在配置文件中配置业务层和持久层对
2.3.2、配置步骤
2.3.2.1:第一步:配置事务管理器
--->即类:org.springframework.jdbc.datasource.DataSourceTransactionManager
该事务管理类中的方法:
<!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入DataSource--> <property name="dataSource" ref="dataSource"></property> </bean>
2.3.2.2、第二步:配置事务的通知引用事务管理器
2.3.2.3、第三步:配置事务的属性
<!--配置事务通知--> <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>
2.3.2.4、第四步:配置AOP切入点表达式
2.3.2.5、第五步:配置切入点表达式和事务通知的对应关系
<!--配置AOP--> <aop:config> <!--配置切入点表达式--> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--建立切入点表达式和事务通知的对应关系--> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config>
2.4、基于注解的配置方式
2.4.1.1、第一步:拷贝必备的jar包到工程到lib目录
2.4.1.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" 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/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"> <!--配置spring创建容器时要扫描的包--> <context:component-scan base-package="com.itheima"></context:component-scan> <!--配置JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置数据源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/eesy_spring"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>
2.4.1.3、第三步:创建数据库表和实体类
2.4.1.4、第四步:创建业务层接口和实现类并使用注解让spring管理
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
// public void setAccountDao(IAccountDao accountDao) {
// this.accountDao = accountDao;
// }
2.4.1.5、第五步:创建Dao接口和实现类并使用注解让spring管理
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
2.4.2、配置步骤
2.4.2.1、第一步:配置事务管理器注入数据
<!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
2.4.2.2、第二步:在业务层使用 @Transactional 注解
/** * 事务控制应该都是在业务层 */ @Service("accountService") @Transactional(propagation = Propagation.SUPPORTS,readOnly = true) //只读型事务的配置 public class AccountServiceImpl implements IAccountService{ //需要的是读写型事务配置 @Transactional(propagation= Propagation.REQUIRED,readOnly=false) @Override public void transfer(String sourceName, String targetName, Float money) { System.out.println("transfer....");
该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口
2.4.2.3、第三步:在配置文件中开启spring对注解事务的支持
<!--开启spring对注解事务的支持--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
2.4、基于纯注解的配置方式
注解开启spring对注解事务的支持--->@EnableTransactionManagement 
三、Spring5的新特性
3.1、JDK相关的升级
3.1.1、jdk版本要求
spring的配置类
spring5.0 在 2017 年 9 月发布了它的 GA(通用)版本。该版本是基于 jdk8 编写的,所以 jdk8 以下版本 将无法使用。同时,可以兼容 jdk9 版本。
在反射创建对象的效率上,jdk8做了加强。
3.2、核心容器的更新
Spring Framework 5.0 现在支持候选组件索引作为类路径扫描的替代方案。该功能已经在类路径扫描器中 添加,以简化添加候选组件标识的步骤。
应用程序构建任务可以定义当前项目自己的 META-INF/spring.components 文件。在编译时,源模型是 自包含的,JPA 实体和 Spring 组件是已被标记的。
从索引读取实体而不是扫描类路径对于小于 200 个类的小型项目是没有明显差异。但对大型项目影响较大。
加载组件索引开销更低。因此,随着类数的增加,索引读取的启动时间将保持不变。 加载组件索引的耗费是廉价的。因此当类的数量不断增长,加上构建索引的启动时间仍然可以维持一个常数, 不过对于组件扫描而言,启动时间则会有明显的增长。
这个对于我们处于大型 Spring 项目的开发者所意味着的,是应用程序的启动时间将被大大缩减。虽然 20 或者 30 秒钟看似没什么,但如果每天要这样登上好几百次,加起来就够你受的了。使用了组件索引的话,就能帮 助你每天过的更加高效。

浙公网安备 33010602011771号