2020:0611 --- Spring(四)

398 阅读22分钟

今日内容

1.Spring中的JdbcTemplate
2.作业
    Spring基于AOP的事务控制
3.Spring中的事务控制
    基于XML的
    基于注解的

1.Spring中的JdbcTemplate

1.1 JdbcTemplate 概述

    它是spring框架中提供的一个对象,是对原始Jdbc API对象的简单封装(对JDBC)进行了
薄薄的封装。Spring矿建为我们提供了很多的操作模板类。
    
    操作关系型数据的:
        JdbcTemplate
        HibernateTemplate
    
    操作nosql数据库的:
        RedisTemplate
    
    操作消息队列的:
        JmsTemplate
    
    我们今天学习的主角JdbcTemplate在 spring-jdbc-5.0.2.RELEASE.jar 中,我们在导包的
时候,除了要导入这个jar包外,还需要导入一个spring-tx-5.0.2.RELEASE.jar(它是和事务
相关的)。

1.2 JdbcTemplate 的作用

        它就是用于和数据库交互的,实现对表的CRUD操作
        
        * jdbcTemplate的最基本用法1.
        ```
        public static void main(String[] args) {

            //准备数据源:spring的内置数据源
            DriverManagerDataSource ds = new DriverManagerDataSource();
            ds.setDriverClassName("com.mysql.jdbc.Driver");
            ds.setUrl("jdbc:mysql://localhost:3306/txl_spring");
            ds.setUsername("root");
            ds.setPassword("root");
    
            //1.创建JdbcTemplate对象
            //JdbcTemplate jt = new JdbcTemplate(ds);
            JdbcTemplate jt = new JdbcTemplate();
    
            //2.给JdbcTemplate设置数据源
            jt.setDataSource(ds);
    
            //2.执行操作
            jt.execute("insert into account(name,money)values('ccc',1000)");
        }
        ```
        
        * jdbcTemplate的最基本用法2  ---  IOC实现
        
        ```
        <?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">
        
        
            <!--配置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/txl_spring"></property>
                <property name="username" value="root"></property>
                <property name="password" value="root"></property>
            </bean>
        
        </beans>
        
        
        public static void main(String[] args) {

            //1.获取容器:
            ApplicationContext container = new ClassPathXmlApplicationContext("bean.xml");
            //2.获取对象
            JdbcTemplate jdbcTemplate = container.getBean("jdbcTemplate", JdbcTemplate.class);
            //3. 执行操作
            jdbcTemplate.execute("insert into account(name,money)values('ccc',2222)");
        }
        ```

1.3 JdbcTemplate的CRUD操作

1 查询query
    
    1 找出我们要用的查询方法:

    2 RowMapper<T> rowMapper:
        是一个接口且是T类型,所以不能直接用。
        
    ```
    List<Account> accounts = jdbcTemplate.query("select * from account where money > ?", 
                new AccountRowMapper(), 1000);
                
    for (Account account : accounts) {
        System.out.println(account);
    }
    ```
    
    3 要传一个RowMapper<T>的实现类:AccountRowMapper,在里面定义我们要的封装策略。
    ```
    class AccountRowMapper implements RowMapper<Account>{
    
        /**
         * 把结果集中的数据封装到Account中,然后由spring把每个Account加到集合中
         * @param rs
         * @param rowNum
         * @return
         * @throws SQLException
         */
        public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
    
            Account account = new Account();
            account.setId(rs.getInt("id"));
            account.setName(rs.getString("name"));
            account.setMoney(rs.getFloat("money"));
    
            return account;
        }
    }
    ```
    
    4 分析一下:第二个参数RowMapper<T> rowMapper
        这是一个接口,定一个我们的封装策略。
        我们要传一个它的实现类:AccountRowMapper,在里面定义我们要的封装策略。
        ```
         class AccountRowMapper implements RowMapper<Account> {
             //1.重写mapRow方法
             public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
                ...
             }
         }
        ``` 
        
    所以我们将定义了封装策略的RowMapper<T>的子类AccountRowMapper传递过去。

2 查询query的简便方法

    List<Account> accounts = jdbcTemplate.query("select * from account where money > ?", 
        new BeanPropertyRowMapper<Account>(Account.class), 1000f);
        
    * 分析下第二个参数:new BeanPropertyRowMapper<Account>(Account.class)
        Account:是我们要封装的对象
        
    * 注意:
        使用BeanProperytRowMapper要求sql数据查询出来的列和实体属性需要一一对应。
        如果数据中列明和属性名不一致,在sql语句中需要用as重新取一个别名
        
    
3 查询一个
    1.用查询所有的方法:返回List<T>
    ```
    List<Account> accounts1 = jdbcTemplate.query("select * from account where id = ?", 
        new BeanPropertyRowMapper<Account>(Account.class), 1);
    System.out.println(accounts1.isEmpty()?"没有内容":accounts1.get(0));
    ```
    2. 用专门的查询一个方法
    ```
    Account account = jdbcTemplate.queryForObject("select * from account where id = ?", 
        new BeanPropertyRowMapper<Account>(Account.class), 4);
    ```
    3.但是实际上由于一个查询所有的方法就可以解决两种情况,所以一般用查询所有方法解决。
    
4. 查询一行一列
    ```
    Long count = jdbcTemplate.queryForObject("select count(*) from account where money > ?",
        Long.class, 1000);
    System.out.println(count);

    ```
    查询出来的结果会给你转成Long类型,用Long是因为实际开发中数据量比较大。

1.4 在dao中定义JdbcTemplate属性:第一种方式

1.4.1 准备实体类
    ```
    public class Account implements Serializable {

        private Integer id;
        private String name;
        private Float money;
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + 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;
        }
    }
    
    ```
1.4.2 在dao中定义JdbcTemplate
    ```
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        /**
         * 根据id查询张哈
         * @param accountId
         * @return
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 根据名称查询账户
         * @param accountName
         * @return
         */
        Account findAccountByName(String accountName);
    
        /**
         * 更新账户
         * @param account
         */
        void updateAccount(Account account);
    }
    
    
    /**
     * 账户的持久层实现类
        持久层实现类中有定义JdbcTemplate的属性和set方法
     */
    public class AccountDaoImpl implements IAccountDao {
    
        private JdbcTemplate jdbcTemplate;
        
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { 
            this.jdbcTemplate = jdbcTemplate; 
        }
        
        public Account findAccountById(Integer accountId) {
            List<Account> accountList = 
                jdbcTemplate.query("select * from account where id = ?", 
                        new BeanPropertyRowMapper<Account>(Account.class), accountId);

            return accountList.isEmpty()?null:accountList.get(0);
        }
        
        public Account findAccountByName(String accountName) {
            List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", 
                new BeanPropertyRowMapper<Account>(Account.class), "ccc");
                
            if(accounts.size() > 1){
                throw new RuntimeException("结果集不唯一");
            }
            if(accounts.isEmpty()){
                return null;
            }
            
            return accounts.get(0);
        }

    ```
1.4.3
    配置文件: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">
    
    
        <!--配置账户的持久层-->
        <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
            <!-- 注入jdbcTemplate --> 
            <property name="jdbcTemplate" ref="jdbcTemplate"></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/txl_spring"></property>
            <property name="username" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
    </beans>
    ```
1.4.4 分析思考
    思考:此种方式有什么问题?
    分析:
        有个小问题,就是当我们有多个dao时,每个dao都有一些重复性的代码,注入和定义的重复代码。
        ```
        private JdbcTemplate jdbcTemplate;
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
        ```
    * 那么我们怎么把它抽取出来呢?

1.5 在dao中使用JdbcTemplate:第二种方式

    让dao继承JdbcDaoSupport:此类用于抽取dao中的重复代码
    
    JdbcDaoSupport是spring框架为我们提供的一个类,该类中定义了一个JdbcTemplate对象属性
。要想获得该属性可以通过两种方式:
        1.注入:DataSource
        2.注入:JdbcTemplate
        
    1.我们分析一下JdbcDaoSupport源码:
        ```
        public abstract class JdbcDaoSupport extends DaoSupport {

    	@Nullable
    	private JdbcTemplate jdbcTemplate;
    
    	public final void setDataSource(DataSource dataSource) {
    		if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {
    			this.jdbcTemplate = createJdbcTemplate(dataSource);
    			initTemplateConfig();
    		}
    	}
    	
    	protected JdbcTemplate createJdbcTemplate(DataSource dataSource) {
    		return new JdbcTemplate(dataSource);
    	}
    	
        public final void setJdbcTemplate(@Nullable JdbcTemplate jdbcTemplate) {
	        this.jdbcTemplate = jdbcTemplate;
	        initTemplateConfig();
        }
        
        public final JdbcTemplate getJdbcTemplate() {
            return this.jdbcTemplate;
        }
        ```
            源码里面有一个JdbcTemplate属性:private JdbcTemplate jdbcTemplate。;
        我们可以通过set方法为其注入,再通过get获取。
            里面还有一个setDataSource方法,可以为其注入dataSource。setDataSource
        方法体内判断是否注入了dataSource,如果注入就创建jdbcTemplate。
        
        
    2.  分析一下dao继承JdbcDaoSupport。
    
        我们让dao继承JdbcDaoSupport,然后在dao中为也有了JdbcTemplate属性等方法。
    在dao中就不用在定义JdbcTemplate属性和set方法。
    
        在bean.xml位dao注入dataSource后,由于继承了JdbcDaoSupport中的setdataSource()
    方法和createJdbcTemplate(dataSource)。就会生成dataSource和JdbcTemplate放到了
    IOC容器中。
    
        
        那么我们也无需在bean.xml再为dao注入JdbcTemplate了。
        也不用再配置JdbcTemplate的bean了。
        
    3. 代码:

    4.思考和分析
    
        思考:两种方式的在dao中使用JdbcTemplate有什么区别。
        
        分析:
            1.在dao中定义JdbcTemplate属性:第一种方式。代码会较臃肿,但是
        适用于所有的配置方式。
            2.让Dao继承JdbcDaoSupport的方式:第一种方式。代码精简了。
        但是只能用XML的方式配置,由于要导入的:
        import org.springframework.jdbc.core.support.JdbcDaoSupport;
        所以不能用注解配置JdbcTemplate属性。

2 作业 Spring基于AOP的事务控制

2.1 XML配置实现

    1.实体类: Account
    ```
    @Data
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Float money;
    }
    ```
    
    2.账户的业务层实现类:AccountServiceImpl
    ```
    public class AccountServiceImpl implements IAccountService {

        private IAccountDao accountDao;
        public void setAccountDao(IAccountDao accountDao){
            this.accountDao = accountDao;
        }

        public void transfer(String sourceName, String targetName, float money) {
    
            System.out.println("transfer...");
                    //1.根据名称查询转出账户
                Account source = accountDao.findAccountByName(sourceName);
                    //2.根据名称查询转入账户
                Account target = accountDao.findAccountByName(targetName);
                    //3.转出账户减钱
                source.setMoney(source.getMoney() - money);
                    //4.转入账户加钱
                target.setMoney(target.getMoney() + money);
                    //5.更新转出账户
                accountDao.updateAccount(source);
    
                //int i = 1/0; //模拟转账异常
    
                    //6.更新转入账户
                accountDao.updateAccount(target);
    
        }
    }
    ```
    
    3.账户的持久层实现类
    ```
    public class AccountDaoImpl implements IAccountDao {
    
        private QueryRunner runner;
        private ConnectionUtils connectionUtils;
    
        public void setConnectionUtils(ConnectionUtils connectionUtils) {
            this.connectionUtils = connectionUtils;
        }
    
        public void setRunner(QueryRunner runner){
            this.runner = runner;
        }
    
        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);
            }
        }
    
        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);
            }
        }
    }
    ```
    
    4.连接工具类
      从数据源中获取一个连接,并且实现和线程的绑定
    ```
    public class ConnectionUtils {
    
        //1.ThreadLocal对象,用泛型指定管理对象是java.sql.Connection
        private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

        @Autowired
        private DataSource dataSource;
    
        public void setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        /**
         * 获取当前线程上的连接
         *     控制事务,是靠connection把手动提交改成自动提交
         *     再通过commit和rollback来控制事务。
         * @return
         */
        public Connection getThreadConnection(){
            try{
                //1.先从ThreadLocal上获取
                Connection coon = tl.get();
                /**
                 * 2.判断当前线程上是否有连接:
                 */
                if(coon == null){
                    //3.从数据源中获取一个连接,并且存入ThreadLocal中
                    coon = dataSource.getConnection();
                    //4.存入ThreadLocal
                    tl.set(coon);
                }
                //5.返回当前线程上的连接
                return coon;
            }catch (Exception e){
                throw  new RuntimeException(e);
            }
        }
    
        /**
         * 把连接和线程解绑
         */
        public void removeConnection(){
            tl.remove();
        }
    }
    ```
    
    5.事务管理类
    ```
    public class TransactionManager {

        @Autowired
        private ConnectionUtils connectionUtils;
        //等待Spring将其注入
        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();
            }
        }
    }

    ```
    
    6.配置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">
            
        <!--配置service对象-->
        <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
        
        <!--配置Dao对象-->
        <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
            <property name="runner" ref="queryRunner"></property>
            <property name="connectionUtils" ref="connectionUtils"></property>
        </bean>
        
        <!--配置QueryRunner对象-->
        <bean id="queryRunner" 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/txl_spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
        
        <!--配置Connection的工具类:ConnectionUtils-->
        <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        
        <!--配置事务管理类:TransactionManager
            也就是提供了重复代码的通知类,将其交给spring来管理。
        -->
        <bean id="transactionManager" class="com.itheima.utils.TransactionManager">
            <property name="connectionUtils" ref="connectionUtils"></property>
        </bean>
        
        
        <!--配置AOP-->
        <aop:config>
            <!--配置一个通用的切入点表达式-->
            <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
            <aop:aspect id="txAdvice" ref="transactionManager">
                <!--配置前置通知:开启事务-->
                <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>
        
    ```

2.2 注解配置实现AOP

2.2.1 前后异终通知---AOP

    1.先改造一下IOC:导入一个context的名称空间和对应约束

    2. 配置spring创建容器时要扫描的包:告知在哪写了注解。
    
    <context:component-scan base-package="com.itheima"></context:component-scan>
    
    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">

        <!--配置spring创建容器时要扫描的包-->
        <context:component-scan base-package="com.itheima"></context:component-scan>

        <!--配置QueryRunner对象-->
        <bean id="queryRunner" 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/txl_spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
        <!--开启spring对注解AOP的支持-->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
    </beans>
    ```


    3.事务管理类
    ```
    @Component("transactionManager")
    @Aspect
    public class TransactionManager {
    
        @Resource(name="connectionUtils")
        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 (Exception 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 (Exception e){
                e.printStackTrace();
            }
        }
    }
    ```
    
    4.连接工具类
    ```
    @Component("connectionUtils")
    public class ConnectionUtils {
    
        private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
        
        @Autowired
        private DataSource dataSource;

        public Connection getThreadConnection(){
    
    
            try{
                //1.先从ThreadLocal上获取
                Connection coon = tl.get();

                if(coon == null){
                    //3.从数据源中获取一个连接,并且存入ThreadLocal中
                    coon = dataSource.getConnection();
                    coon.setAutoCommit(false);
                    //4.存入ThreadLocal
                    tl.set(coon);
                }
                //5.返回当前线程上的连接
                return coon;
            }catch (Exception e){
                throw  new RuntimeException(e);
            }
        }
    
        public void removeConnection(){
            tl.remove();
        }
    }
    ```
    
    5.业务层
    ```
    @Service("accountService")
    public class AccountServiceImpl implements IAccountService {
    
        @Autowired
        private IAccountDao accountDao;
    
        public List<Account> findAllAccount() {
            return  accountDao.findAllAccount();
        }
    
        public Account findAccountById(Integer accountId) {
                return accountDao.findAccountById(accountId);
        }
    
        public void saveAccount(Account account) {
            accountDao.saveAccount(account);
        }
    
        public void updateAccount(Account account) {
            accountDao.updateAccount(account);
        }
    
        public void deleteAccount(Integer accountId) {
            accountDao.deleteAccount(accountId);
        }
    
        public void transfer(String sourceName, String targetName, float money) {
    
            System.out.println("transfer...");
                    //1.根据名称查询转出账户
                Account source = accountDao.findAccountByName(sourceName);
                    //2.根据名称查询转入账户
                Account target = accountDao.findAccountByName(targetName);
                    //3.转出账户减钱
                source.setMoney(source.getMoney() - money);
                    //4.转入账户加钱
                target.setMoney(target.getMoney() + money);
                    //5.更新转出账户
                accountDao.updateAccount(source);
    
                //int i = 1/0; //模拟转账异常
    
                    //6.更新转入账户
                accountDao.updateAccount(target);
    
        }
    }
    ```
    
    6.持久层...
    

    
    
    7. 分析:
        先回想一下,注解实现AOP的一个小问题:
            先调用最终通知,再调用异常通知/后置通知。
        
        所以先调用最终通知就会将coon关闭了,再将coon与当前线程解绑。
        ```
        @After("pt1()")
        public void release(){
            try{
                connectionUtils.getThreadConnection().close();
                //解绑
                connectionUtils.removeConnection();
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        ```
        那么再调用后置通知时:会先调用connectionUtils.getThreadConnection().commit();
    发现没有连接了,它会再从数据源中拿一个新的coon然后绑上去。
    
        为什么可以拿新的coon,因为我们在ConnectionUtils类中写的方法:
        connectionUtils.getThreadConnection()
         
        前置通知执行完了(因为前置通知会将autocommit置false),才从数据源中拿一个新的
    coon绑上去。注意从数据池中得到的新的coon的autocommit=true,所以再提交commit就会报错:
        Can't call commit when autocommit=true
        
        那我们在ConnectionUtils.getThreadConnection()获取新的coon时,将其AutoCommit设为false。
    设置成false之后,那么可以提交了,但是数据库中的数据会改吗?
        因为刚才操作的那个coon被先关闭了,而现在这个新的coon虽然你提交,没有异常产生了。但是
    新的coon里面没有任何的执行操作,什么都提交不了。 
        
   
   8. 所以这就是Spring基于注解的配置AOP时:通知的执行调用是有问题的,
      如果非要用注解:我们用环绕通知。
2.2.2 环绕通知 -- 注解实现AOP
    在事务管理类中,不配置前置通知/后置通知/异常通知/最终通知等注解。
    
    * 配置一个环绕通知:

3.Spring提供的工具来进行事务控制---准备工作

3.1 事务控制我们要明确的

1. JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。
2. spring框架为我们提供了一组事务控制的接口。具体在后面的第二小节介绍。这组接口是在
    spring-tx-5.0.2.RELEASE.jar中。
    所以要先导入依赖
    
3. spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。
    
4. 注意:我们学习的重点是使用配置的方式实现。

3.2 PlatformTransactionManager:事务管理类接口

    此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法,如下图:

    我们在开发中都是使用它的实现类,真正管理事务的对象:
        * org.springframework.jdbc.datasource.DataSourceTransactionManager
        使用Spring JDBC或iBatis 进行持久化数据时使用
        
        * org.springframework.orm.hibernate5.HibernateTransactionManager
        使用Hibernate版本进行持久化数据时使用

3.3 事务信息类接口: 它是事务的定义信息对象,里面有如下方法:

    1. 获取事务对象名称: String getName()
    
    2. 获取事务隔离级: int getIsolationLevel()
        事务的隔离级别一共有四个,spring默认情况下使用的是数据库的默认隔离级别。

    3.获取事务传播行为: int getPropagationBehavior()
        什么情况下必须有事务,什么情况下可以没有事务。
        我们暂时考虑:增删改必须有事务。
                      查询时可以没有事务。

    4.指定事务的超时时间: int getTimeout()
        提交回滚多少时间后就过期了:我们可以通过配置的方式永远斗不过期。

    5.获取事务是否只读: boolean isReadOnly()
        只读:执行查询时,也会开启事务
        读写型事务:增删改,开启事务。

3.4 TransactionStatus接口:事务状态

    此接口提供的是事务具体的运行状态,方法介绍如下图:

4. Spring自己的基于XML的事务控制

    步骤:
        1. 配置spring的事务管理器
            这个里面有提交和回滚等方法。然后在第2步,指明其是通知。
        2. 配置事务的通知
            此时我们要导入事务的约束: tx的名称空间和约束,同时也需要aop的约束  xmlns:tx
            使用tx:advice配置事务通知
                属性:
                    id:给事务通知起一个唯一标志
                    transaction-manager:给事务通知提供一个事务管理器引用
        3. 配置AOP中的通用切入点表达式
        4. 建立事务通知和切入点表达式的对应关系
        5. 配置事务的属性   
            是在事务的通知tx:advice标签的内部
            
    明确:因为用的是spring自带的事务管理器,里面有提交和回滚等方法。所以我们只要指定管理器
          和切入点方法的联系即可。不用我们在设置里面具体的执行步骤。
    
    ```
    <!--1.配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!--2.配置事务的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--4.为service中的所有方法,配置事务的属性
                isolation=""        :       指定事务的隔离级别,默认值DEFAULT表示使用数据库的默认隔离级别
                no-rollback-for=""  :       用于指定一个异常,产生该异常时不回滚,其他异常会滚。没有默认值,表示任何异常都会滚。
                propagation=""      :       指定事务的传播级别:
                                                默认REQUIRED:没有就新建一个,有就添加进去。适合增删改。
                                                SUPPORTS:没事务就用,没有就不用。适合查询

                read-only=""        :       事务是否只读:查询方法可以设为只读true,增删改设为读写。
                rollback-for=""     :       用于指定一个异常,产生该异常时回滚,其他异常不会滚。没有默认值,表示任何异常都会滚。
                timeout=""          :       指定事务的超时时间:,默认-1,永不超时。指定数值以秒为单位。
            -->
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <!--为service中的findxxx()方法,配置事务的属性
                    这个优先级是高于上面的全通配的
            -->
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    
    <!--3.配置AOP-->
    <aop:config>
        <!--3.1配置通用点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        <!--3.2建立切入点表达式和事务通知的对应关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>
    ```

5. Spring基于注解和XML结合的事务控制

    引入相应的名称空间和约束

    1.配置事务管理器
    ```
    <!--1.配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    ```
    
    2.开启spring对注解事务的支持
    ```
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    ```
    
    3.在需要事务支持的地方使用@Transactional注解

    不仅仅在类上,可以指定到具体的方法

    4.完整的bean.xml:注解和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: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/txl_spring"></property>
            <property name="username" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
    
        <!--spring基于注解的声明式事务控制 配置步骤
                1.配置事务管理器
                2.开启spring对注解事务的支持
                3.在需要事务支持的地方使用@Transactional注解
        -->
        <!--1.配置事务管理器-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!--2.开启spring对注解事务的支持-->
        <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    </beans>
    ```

6. Spring基于注解的事务控制 (没有bean.xml)

    1.账户的持久层实现类
    ```
    @Repository("accountDao")
    public class AccountDaoImpl implements IAccountDao {
        @Autowired
        private JdbcTemplate jdbcTemplate;
        
    //1.按方法名查询一个
    public Account findAccountByName(String accountName) {
    }   
    
    //2.更新账户
    public void updateAccount(Account account) {
    }
    ```
    
    2.账户的业务层实现类:让事务控制和业务层的方法分离
      在需要事务支持的地方使用@Transactional注解
    ```
    @Service("accountService")
    @Transactional                  //事务的属性全在里面:read-only/timeout/isolation...
    public class AccountServiceImpl implements IAccountService {
    
        @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
        public void transfer(String sourceName, String targetName, Float money) {
        
            Account source = accountDao.findAccountByName(sourceName);

            Account target = accountDao.findAccountByName(targetName);

            source.setMoney(source.getMoney() - money);
  
            target.setMoney(target.getMoney() + money);
  
            accountDao.updateAccount(source);

            //int i = 1/0; //模拟转账异常

            accountDao.updateAccount(target); 
        }
    }
    ```
    
    3. 连接资源配置文件:jdbcConfig.properties
    ```
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/txl_spring
    jdbc.username=root
    jdbc.password=root
    ```
    
    4. 3个配置类
        主配置类:public class SpringConfiguration,引入子配置类
        子配置类1:public class JdbcConfig,和连接数据库相关的配置类
        子配置类2:public class TransactionConfig,和事务相关的配置类
        
        4.1: 子配置类1:public class 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对象
            @Bean(name="jdbcTemplate")
            public JdbcTemplate createJdbcTemplate(DataSource dataSource){
                return new JdbcTemplate(dataSource);
            }
        
            //创建数据源对象
            @Bean(name="dataSource")
            public DataSource createDataSource(){
                DriverManagerDataSource ds = new DriverManagerDataSource();
                ds.setDriverClassName(driver);
                ds.setUrl(url);
                ds.setPassword(password);
                ds.setUsername(username);
        
                return ds;
            }
        }
        ```
        
        4.2: 子配置类2:public class JdbcConfig,public class TransactionConfig,和事务相关的配置类
        
        ```
        public class TransactionConfig {
        
            /**
             * 创建管理器对象
             * PlatformTransactionManager  接口
             * new DataSourceTransactionManager(dataSource);    返回它的子类
             * @param dataSource
             * @return
             */
            @Bean(name="transactionManager")
            public PlatformTransactionManager createTransactionManager(DataSource dataSource){
                return new DataSourceTransactionManager(dataSource);
            }
        }
        ```
        
        4.3 : 主配置类:public class SpringConfiguration
        
        ```
        /**
         * spring的配置类,相当于bean.xml
         */
        @Configuration  //这个注解可写可不写:因为可以把这个类当参数传递给测试方法
        @ComponentScan("com.itheima")
        @Import({JdbcConfig.class,TransactionConfig.class})
        @PropertySource("classpath:jdbcConfig.properties")  //指明连接资源
        @EnableTransactionManagement   //开启注解支持
        public class SpringConfiguration {
        
        }
        ```
    
    5. 测试方法---spring整合的junit
    
    ```
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfiguration.class)
    public class AccountServiceTest {
    
        @Autowired
        private IAccountService accountService;
    
        @Test
        public void testTransfer(){
            accountService.transfer("bbb", "ccc", 100f);
    
        }
    }
    ```

7. 编程式的事务控制:代码实现的事务控制(用的很少)

   上面是Spring声明类型的的事务控制。
   
   1.事务的控制都离不开 提交和回滚这两个操作。在spring中将提交和回滚放到了事务管理器中,
  
     所以无论如何我们先配置一个事务管理器,且需要注入数据源
     ```
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
     ```
     
    2.事务模版对象
    ```
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"></bean>
    ```
    
    我们在要使用事务的地方添加一个事务模版对象属性。
    ```
    public class AccountServiceImpl implements IAccountService {
        private TransactionTemplate transactionTemplate;
    
        public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
            this.transactionTemplate = transactionTemplate;
        }
        
    }
    ```
    
    3.再仿照TransactionTemplate中的public <T> T execute(TransactionCallback<T> action)来进行编程式
事务控制。
     serviec层中用到的方法全都要编写代码

    * public class AccountServiceImpl
    
    ```
public class AccountServiceImpl implements IAccountService {

    private TransactionTemplate transactionTemplate;

    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }
    
    public Account findAccountById(final Integer accountId) {

        return transactionTemplate.execute(new TransactionCallback<Account>() {
            public Account doInTransaction(TransactionStatus status) {

                return accountDao.findAccountById(accountId);
            }
        });

    }


    public void transfer(final String sourceName, final String targetName, final Float money) {

        transactionTemplate.execute(new TransactionCallback<Object>() {
            public Object doInTransaction(TransactionStatus status) {

                System.out.println("transfer...");
                //1.根据名称查询转出账户
                Account source = accountDao.findAccountByName(sourceName);
                //2.根据名称查询转入账户
                Account target = accountDao.findAccountByName(targetName);
                //3.转出账户减钱
                source.setMoney(source.getMoney() - money);
                //4.转入账户加钱
                target.setMoney(target.getMoney() + money);
                //5.更新转出账户
                accountDao.updateAccount(source);

                //int i = 1/0; //模拟转账异常

                //6.更新转入账户
                accountDao.updateAccount(target);
                return null;
            }
        });
    }
}
    ```
    
    
    4. 在业务层注入事务模版对象
       在事务模版对象注入事务管理器对象
    ```
        <!--配置业务层-->
        <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
            <property name="transactionTemplate" ref="transactionTemplate"></property>
        </bean>

    <!--2.事务模版对象-->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>
    ```
    
    5. 问题:由于是编写代码实现的事务控制,会导致业务层的重复代码变得更多。
       所以出场机会不高。

8. SPRING 5新特性。

8.1 JDK版本要求

    spring5.0在2017年9月发布了它的GA(通用)版本。该版本是基于jdk8编写的,所以jdk8以下版本
将无法使用。同时,可以兼容jdk9版本。
    tomcat版本要求8.5及以上。
    
    我们使用jdk8构建工程,可以降版编译。但是不能使用jdk8以下版本构建工程。
    
    由于jdk和tomcat版本的更新,我们的IDE也需要同时更新。

8.2 利用jdk8版本更新的内容

1. 基于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", int.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(int age) {
            this.age = age;
        }
    }
}

```

2. @NonNull注解和@Nullable注解的使用

    用 @Nullable 和 @NotNull注解来显示表明可为空的参数和以及返回值。这样就够在编译的时候
处理空值而不是在运行时抛出 NullPointerExceptions。

3. 第三:日志记录方面
    Spring Framework 5.0 带来了 Commons Logging 桥接模块的封装, 它被叫做 spring-jcl 而不
是标准的 Commons Logging。当然,无需任何额外的桥接,新版本也会对 Log4j 2.x, SLF4J, JUL ( java.util.logging) 进行自动检测。


4. 核心容器的更新