• Spring(四)-- JdbcTemplate、声明式事务


    1.Spring提供的一个操作数据库的技术JdbcTemplate,是对Jdbc的封装。语法风格非常接近DBUtils。
      JdbcTemplate可以直接操作数据库,加快效率,而且学这个JdbcTemplate也是为声明式事务做准备,毕竟要对数据库中的数据进行操纵!
      JdbcTemplate中并没有提供一级缓存,以及类与类之间的关联关系!就像是spring提供的一个DBUtils。
      Spring对数据库的操作使用JdbcTemplate来封装JDBC,结合Spring的注入特性可以很方便的实现对数据库的访问操作。使用JdbcTemplate可以像JDBC一样来编写数据库的操作代码
     
    2.为啥要使用Jdbc_template进行开发呢?
        Spring对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中。
        Spring提供的JdbcTemplate对jdbc做了封装,大大简化了数据库的操作。找到Spring JdbcTemplate源码,可以看到如下方法:
            Connection con = DataSourceUtils.getConnection(getDataSource());
        如果直接使用JDBC的话,需要我们加载数据库驱动、创建连接、释放连接、异常处理等一系列的动作;繁琐且代码看起来不直观。
        此外,Spring提供的JdbcTempate能直接数据对象映射成实体类,不再需要获取ResultSet去获取值/赋值等操作,提高开发效率;
     
    3.配置环境
    ①导入jar包
      [1]IOC容器需要的jar包   
                commons-logging-1.1.3.jar
                spring-aop-4.0.0.RELEASE.jar  //注解会使用到的包
                spring-beans-4.0.0.RELEASE.jar
                spring-context-4.0.0.RELEASE.jar
                spring-core-4.0.0.RELEASE.jar
                spring-expression-4.0.0.RELEASE.jar
     
      [2]MySQL驱动、C3P0jar包
                c3p0-0.9.1.2.jar
                mysql-connector-java-5.1.37-bin.jar
     
      [3]JdbcTemplate需要的jar包   
                spring-jdbc-4.0.0.RELEASE.jar
                spring-orm-4.0.0.RELEASE.jar
                spring-tx-4.0.0.RELEASE.jar
     
    ②在IOC容器中配置数据源
     
    <!-- 加载properties文件中 信息 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 配置数据源 -->
    <bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    	<property name="user" value="${jdbc.username}"></property>
    	<property name="password" value="${jdbc.password}"></property>
    	<property name="driverClass" value="${jdbc.driver}"></property>
    	<property name="jdbcUrl" value="${jdbc.url}"></property>
    </bean> 
    	
    

     其中,新建 jdbc.properties 文件

    内容:

    jdbc.username=root
    jdbc.password=123456
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/jdbc_template
    

    ③在IOC容器中配置JdbcTemplate对象的bean,并将数据源对象装配到JdbcTemplate对象中

    <!-- 配置JdbcTemplate对应的bean, 并装配dataSource数据源属性-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
          <property name="dataSource" ref="comboPooledDataSource"></property>
    </bean>
    

    实验1:测试数据源
     
    @Test
    public void test() throws SQLException{
           ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
           DataSource bean = ioc.getBean(DataSource.class);
           System.out.println(bean.getConnection());
    }
    

     

    实验2:将emp_id=5的记录的salary字段更新为1300.00【更新操作】

    不用我们自己再去获取数据库连接信息了,而是直接传递sql语句及其参数!
    public class TestJDBCTemplate {
    
          private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
          private JdbcTemplate bean = ioc.getBean(JdbcTemplate.class);
    
          @Test
          public void test() throws SQLException{
                String sql = "UPDATE employee SET salary = ? WHERE emp_id = ?";
                bean.update(sql, 1300,5);//第一个是sql语句,后面的按着顺序传入参数即可,这个update方法是接收的可变参数!
          }
    }
    
     实验3:批量插入  bean.batchUpdate(sql, list);
     
    public class TestJDBCTemplate {
    
          private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
          private JdbcTemplate bean = ioc.getBean(JdbcTemplate.class);
          
          @Test
          public void test() throws SQLException{
                String sql="INSERT INTO employee(emp_name,salary) VALUES(?,?)";
    
                List<Object[]> list = new ArrayList<Object[]>();
                list.add(new Object[]{"Tom2015",1000});
                list.add(new Object[]{"Tom2016",2000});
                list.add(new Object[]{"Tom2017",3000});
    
                bean.batchUpdate(sql, list);
          }
    }
    
     实验4:查询emp_id=5的数据库记录,封装为一个Java对象返回
     
     分析:封装为一个对象返回的话,首先我们需要有一个与数据表对应的实体类!
    public class TestJDBCTemplate {
    
          private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
          private JdbcTemplate bean = ioc.getBean(JdbcTemplate.class);
         
          @Test
          public void test(){
                //需要注意的是:sql语句中的别名要与对应实体类的属性名保持一致!
                String sql = "SELECT emp_id AS empId,emp_name AS empName,salary FROM employee WHERE emp_id=?";
    
                //RowMapper是一个接口,这里我们使用其子类
                RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<Employee>(Employee.class);
                //最后一个参数是可变参数,用于向sql语句中依次传递参数!
                Employee employee = bean.queryForObject(sql, rowMapper, 5);
                System.out.println(employee);
          }
    }
    

    实验5:查询salary>4000的数据库记录,封装为List集合返回

     
    可以看出,查询结果是一个实体还是一个list列表是靠bean对象的不同方法实现的!
    public class TestJDBCTemplate {
    
          private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
          private JdbcTemplate bean = ioc.getBean(JdbcTemplate.class);
    
          @Test
          public void test(){
             //需要注意的是:sql语句中的别名要与对应实体类的属性名保持一致!
               String sql = "SELECT emp_id AS empId,emp_name AS empName,salary FROM employee WHERE salary > ?";
    
               //RowMapper是一个接口,这里我们使用其子类
               RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<Employee>(Employee.class);
               //该query方法查询出来的是一个list列表,query方法的最后一个参数是可变参数!
               List<Employee> list = bean.query(sql, rowMapper, 4000);
      
               for (Employee employee : list) {
                    System.out.println(employee);
               }
         }    
    }
    

    实验6:查询最大salary

     
    public class TestJDBCTemplate {
    
        private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        private JdbcTemplate bean = ioc.getBean(JdbcTemplate.class);
    
        @Test
        public void test01(){
             String sql = "SELECT MAX(salary) FROM employee";
             //需要指定返回值的类型,而且类型必须是包装类型
             Double maxSalary = bean.queryForObject(sql, Double.class);
             System.out.println(maxSalary);
        }
    }
    

    实验7:使用带有具名参数的SQL语句插入一条员工记录,并以Map形式传入参数值

     
    具名参数:是指基于名称的,前面我们使用的都是用 ? 作为占位符,是基于位置的!
    如果要使用具名参数的sql语句就必须在spring配置文件中配置NamedParameterJdbcTemplat这个模板类
    原来的JdbcTemplate,因为JdbcTemplate不能完成这样的任务!
                  
     在 applicationContext.xml 文件中继续配置     
    <!-- 为了执行带有具名参数的SQL语句,需要配置NamedParameterJdbcTemplate -->
    <!-- 该NamedParameterJdbcTemplate类没有无参构造器,需要传入JdbcTemplate对象或者数据源对象[DataSource] -->
    <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <!-- 不能使用property标签配置 -->
        <constructor-arg ref="jdbcTemplate"></constructor-arg>
    </bean>
    
     
    public class TestJDBCTemplate {
    
          private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    
          @Test
        public void test01(){
             NamedParameterJdbcTemplate bean2 = ioc.getBean(NamedParameterJdbcTemplate.class);
             String sql="INSERT INTO employee(emp_name,salary) VALUES(:paramName,:paramSalary)";//具名参数与map 的key 值相同
             Map<String,Object> paramMap = new HashMap<String,Object>();
             paramMap.put("paramName","张学友" );
             paramMap.put("paramSalary",1000);
    
             bean2.update(sql, paramMap);
        }
    }
    

    实验8:重复实验7,以SqlParameterSource形式传入参数值

     
    public class TestJDBCTemplate {
    
          private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    
          @Test
        public void test01(){
             NamedParameterJdbcTemplate bean2 = ioc.getBean(NamedParameterJdbcTemplate.class);
             String sql="INSERT INTO employee(emp_name,salary) VALUES(:empName,:salary)";
             //该BeanPropertySqlParameterSource类构造器需要一个对象参数,该对象参数是一个封装了sql语句参数的对象!
             //此时要求对象的属性名要和sql中的参数名保持一致!这里我们使用Employee对象来完成
             Employee employee= new Employee("郭富城", 1500);
             //以实体对象的形式封装具名参数和值
             SqlParameterSource source = new BeanPropertySqlParameterSource(employee);
    
             bean2.update(sql, source);
        }
    }
    
     实验9:创建JdbcTemplateDao,自动装配JdbcTemplate对象
     
     1.创建dao类:
    @Repository
    public class JdbcTemplateDao {
         @Autowired
         private JdbcTemplate jdbcTemplate;
    
         public void update(){
             String sql = "DELETE FROM employee where emp_id = ?";
             jdbcTemplate.update(sql, 15);
         }
    }
    
     2.测试该dao 
    public class TestJDBCTemplate {
    
          private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
          private JdbcTemplate bean = ioc.getBean(JdbcTemplate.class);
    
          private JdbcTemplateDao dao = ioc.getBean(JdbcTemplateDao.class);
          @Test
          public void testEmployeeDao(){
               dao.update();
          }
    }
    
     
     声明式事务:
     
    1.导jar包:
    - com.springsource.net.sf.cglib-2.2.0.jar
    - com.springsource.org.aopalliance-1.0.0.jar
    - com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
    - commons-logging-1.1.3.jar
    - spring-aop-4.0.0.RELEASE.jar
    - spring-aspects-4.0.0.RELEASE.jar
    - spring-beans-4.0.0.RELEASE.jar
    - spring-context-4.0.0.RELEASE.jar
    - spring-core-4.0.0.RELEASE.jar
    - spring-expression-4.0.0.RELEASE.jar
    - spring-jdbc-4.0.0.RELEASE.jar
    - spring-orm-4.0.0.RELEASE.jar
    - spring-tx-4.0.0.RELEASE.jar
    - c3p0-0.9.1.2.jar
    - mysql-connector-java-5.1.37-bin.jar
     
     
    2.创建一份jdbc.properties文件 :
    jdbc.user=root
    jdbc.passowrd=123456
    jdbc.url=jdbc:mysql://localhost:3306/tx
    jdbc.driver=com.mysql.jdbc.Driver
     
     
    3.配置文件,在 JdbcTemplate的配置信息的基础上
     
    <context:component-scan base-package="com.neuedu"></context:component-scan>
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
          <property name="user" value="${jdbc.username}"></property>
          <property name="password" value="${jdbc.password}"></property>
          <property name="driverClass" value="${jdbc.driver}"></property>
          <property name="jdbcUrl" value="${jdbc.url}"></property>
    </bean>
    <!-- 以上时JdbcTemplate配置所需 -->
    
    
    <!-- 配置事务管理器 -->
    <bean  id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
         <property name="dataSource" ref="comboPooledDataSource"></property>
    </bean>
    
    <!-- 开启基于注解的声明式事务 -->
    <!-- 有时候不需要transaction-manager 因为默认值为transactionManager -->
    <!-- 如果事务管理器的id为transactionManager就不用写 -->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
    
     4.在需要进行事务控制的方法上加注解:@Transactional
     
     
    5.测试数据源
     
    public class TestDataSource {
    
           private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    
           @Test
           public void test() throws Exception {
                 DataSource bean = ioc.getBean(DataSource.class);
                 System.out.println(bean.getConnection());
           }
    }
    
     6.创建 Dao 类
     
      Dao 层的四个方法
    @Repository
    public class BookDao {
    	
    	@Autowired
    	private JdbcTemplate template;
    	
    	public double findPriceByIsbn(String isbn){
    		String sql = "SELECT price FROM book WHERE isbn = ?";
    		Double price = template.queryForObject(sql, Double.class, isbn);
    		return price;
    	}
    	
    	public void updateBookStock(String isbn){
    		String sql="UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
    		template.update(sql, isbn);
    	}
    	
    	public void updateAccount(String username,Double price){
    		String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
    		template.update(sql, price,username);
    	}
    	
    	
    	//演示事务的传播机制
    	public void updateBookPrice(double price,String isbn){
    		String sql ="UPDATE book SET price = ? WHERE isbn = ?";
    		template.update(sql, price,isbn);
    	}
    }
    

    7.创建 Service 层

    @Service
    public class BookService {
    
    	@Autowired
    	private BookDao bookDao;
    	
    	@Transactional
    	public void doCash(String isbn,String username){
    		double price = bookDao.findPriceByIsbn(isbn);
    		bookDao.updateBookStock(isbn);
    		try {
    			Thread.sleep(1000*5);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		bookDao.updateAccount(username, price);
    	}
    	
    	@Transactional
    	public void updateBookPrice(double price,String isbn){
    		bookDao.updateBookPrice(price, isbn);
    	}
    }
    
     8.测试 Service 中的 doCash 方法:
     
    public class TestBookService {
    	
    	private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    	@Test
    	public void test() {
    		BookService bean = ioc.getBean(BookService.class);
    		bean.doCash("ISBN-001", "Tom");
    	}
    }
    
     
     事务的设置
      - 事务的传播行为
      - 事务的隔离级别
      - 事务根据什么异常不进行回滚
      - 事务的超时属性
      - 事务的只读属性
     
    事务的传播行为:   -----propagation
     
    - REQUIRED :【默认】
    如果外层有事务,就会调用外层的事务
    如果外层没事务,就自己启动个事务,在自己的事务中运行
    当第二个发生异常,第一个也跟着回滚
     
    @Transactional(propagation=Propagation.REQUIRED)
    public void doCash(String isbn,String username){
          double price = bookDao.findPriceByIsbn(isbn);
          bookDao.updateBookStock(isbn);
          bookDao.updateAccount(username, price);
    }
    
    @Transactional(propagation=Propagation.REQUIRED)
    public void updateBookPrice(double price,String isbn){
          bookDao.updateBookPrice(price, isbn);
    }
    
    - REQUIRES_NEW:
    一定会运行自己的事务
    如果外层有事务,那就挂起外层事务
    等到自己的事务运行完毕,在继续外层事务
    当第二个发生异常,也不会回滚
     
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void doCash(String isbn,String username){
          double price = bookDao.findPriceByIsbn(isbn);
          bookDao.updateBookStock(isbn);
          bookDao.updateAccount(username, price);
    }
    
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void updateBookPrice(double price,String isbn){
          bookDao.updateBookPrice(price, isbn);
    }
    
    - SUPPORTS :
    如果外层有事务,那么就在外层事务中运行
    如果没有,自己也不启动新事务
     
     
    总结:


     
    事务的隔离级别:  ----- isolation
     
    比如,当前数据库中的price 为100,输出100;
    然后将 price 改为 200,再输出还会是100,默认是REPEATABLE_READ,保持事务数据一致性
    改为:READ_COMMITTED,就会随着数据库中的数据的更改,输出也更改
     
    @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED)
    public void doCash(String isbn,String username){
         double price = bookDao.findPriceByIsbn(isbn);
         System.out.println(price);
         bookDao.updateBookStock(isbn);
         bookDao.updateAccount(username, price);
    }
    
    不回滚: ------- noRollbackFor={ ArithmeticExpection.class }
    默认出现异常回滚,设置之后出现异常不回滚
    花✿括号内写异常名称
    @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED
                                  ,noRollbackFor={ArithmeticException.class}
                                  )
    public void doCash(String isbn,String username){
          double price = bookDao.findPriceByIsbn(isbn);
          bookDao.updateBookStock(isbn);
          int i = 10/0;//异常
          bookDao.updateAccount(username, price);
    }
    
    事务的超时属性:  ------ tiomeout
    事务超时=事务开始时到最后一个Statment创建时时间 + 最后一个Statment 的执行时超市时间
     
    事务超时时间设置为3秒,sql执行时间为1秒时,事务的不超时的
    事务超时时间设置为3秒,sql执行时间为5秒时,这样事务超时!
    @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED
                                  ,noRollbackFor={ArithmeticException.class}
                                  ,timeout=3)
    public void doCash(String isbn,String username){
           double price = bookDao.findPriceByIsbn(isbn);
           bookDao.updateBookStock(isbn);
           try {
                 Thread.sleep(1000*5);
               } catch (InterruptedException e) {
                      e.printStackTrace();
                }
           bookDao.updateAccount(username, price);
    }
    
    事务的只读属性: -----readOnly
     
    查询性能更高

     
     
  • 相关阅读:
    机器学习技法笔记:16 Finale
    机器学习技法笔记:15 Matrix Factorization
    机器学习技法笔记:14 Radial Basis Function Network
    机器学习技法笔记:13 Deep Learning
    机器学习技法笔记:Homework #7 Decision Tree&Random Forest相关习题
    [HTML] 条件注释判断浏览器
    [ligerUI] grid行编辑示例
    [ligerUI] grid封装调用方法
    [MVC.NET] Asp.Net MVC3 简单入门第一季
    [HTML5] 飞龙天惊-HTML5学习系列
  • 原文地址:https://www.cnblogs.com/lwj-0923/p/7460221.html
Copyright © 2020-2023  润新知