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;
    }    
    
}    
JdbcTemplate源码

除了默认的构造方法,都需要提供一个数据源。存在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());
    }

}
JdbcDaoSupport源码

以下是自己实现的代码:

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

账户持久层实现类,继承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());
    }
}
AccountDaoImpl
<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>
bean.xml

上述两种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>
bean.xml
/**
 * 和事务相关的工具类。包含了:开启事务,提交事务,关闭事务,回滚事务
 */
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();
        }
    }
    
    
}
TransactionManager
/**
 * 账户的业务层实现类
 */
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);
    }
}
AccountServiceImpl
/**
 * 账户的持久层实现类
 */
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);
        }
    }
}
AccountDaoImpl
/**
 * 连接工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
 */
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();
    }
    
}
ConnectionUtils


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>
bean.xml
@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();
        }
    }
    
}
TransactionManager
@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);
    }
}
AccountServiceImpl
@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);
        }
    }
}
AccountDaoImpl
@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();
    }
    
}
ConnectionUtils


二、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>
pom.xml

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 秒钟看似没什么,但如果每天要这样登上好几百次,加起来就够你受的了。使用了组件索引的话,就能帮 助你每天过的更加高效。
核心容器的更新

 

posted @ 2019-01-05 10:23  payn  阅读(456)  评论(0)    收藏  举报