SSH框架之旅-spring(3)

作者: Wizey | 来源:发表于2017-10-14 21:42 被阅读56次
    spring.jpg

    1.Spring 的 jdbcTemplate操作


    Spring 框架是一站式的框架,针对 JavaEE 的三层结构,每一层都有解决的技术,在 DAO(数据操作层)使用 jdbcTempalte。并且 Spring 也是一个胶水式的框架,对于第三方的框架也有很好的整合支持,对不同的持久层技术都进行封装。

    ORM 持久化技术 模板类
    JDBC org.springframework.core.JdbcTemplate
    Hibernate5.x org.springframework.hibernate5.HibernateTemplate
    IBatis(MyBatis) org.springframework.orm.ibatis.sqlMapClientTemplate
    JPA org.springframework.orm.jpa.JpaTemplate

    下面是 Spring 对数据库的 CURD 操作。

    1.1 准备工作

    Spring 对 jdbc 做了封装,需要再之前 Spring 的 jar 包的基础上导入 spring-jdbc.jarspring-tx.jar 包,另外记得导入数据库的驱动包。

    创建数据库和数据表,Spring 中的 JdbcTemplate 只是对 Jdbc 做了封装,不会为我们自动创建数据表

    数据表结构.png

    实体类

    package cc.wenshixin.jdbc;
    
    public class Student {
        private int id;
        private String name;
        private String sex;
        
        public void setId(int id) {
            this.id = id;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    
        @Override
        public String toString() {
            return "Student [id=" + id + ", name=" + name + ", sex=" + sex + "]";
        }
    }
    

    增删改操作都是调用 update() 方法,Spring 中重载了很多 update() 方法。

    1.2 增加操作

        @Test
        //添加操作
        public void add()
        {
            //设置数据库信息
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
            dataSource.setUsername("root");
            dataSource.setPassword("root");
            
            //创建jdbcTemplate对象,设置数据源
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            
            //调用JdbcTemplate对象里面的方法实现操作
            String sql = "INSERT INTO student(name,sex) VALUES(?,?)";
            int rows = jdbcTemplate.update(sql, "小明", "男");
            System.out.println(rows);
        }
    

    1.3 删除操作

        @Test
        //删除操作
        public void delete()
        {
            //设置数据库信息
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
            dataSource.setUsername("root");
            dataSource.setPassword("root");
            
            //创建jdbcTemplate对象,设置数据源
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            
            //调用JdbcTemplate对象里面的方法实现操作
            String sql = "DELETE FROM student WHERE id=?";
            int rows = jdbcTemplate.update(sql, 2);
            System.out.println(rows);
        }
    

    1.4 修改操作

        @Test
        //修改操作
        public void update()
        {
            //设置数据库信息
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
            dataSource.setUsername("root");
            dataSource.setPassword("root");
            
            //创建jdbcTemplate对象,设置数据源
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            
            //调用JdbcTemplate对象里面的方法实现操作
            String sql = "UPDATE student SET name=? WHERE id=?";
            int rows = jdbcTemplate.update(sql, "小张", 2);
            System.out.println(rows);
        }
    

    1.5 查询操作

    基础的jdbc查询操作,必会,虽然使用框架不会使用这些基础的代码,但是在无法使用框架时,也可使用基础的方法实现。

        //使用jdbc操作数据库的基本步骤
        @Test
        public void testJdbc() throws SQLException
        {
            Connection conn = null;
            PreparedStatement psmt = null;
            ResultSet rs = null;
            
            try {
                //加载驱动
                Class.forName("com.mysql.jdbc.Driver");
                //创建连接
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false", "root", "root");
                //编写sql语句
                String sql = "SELECT * FROM student WHERE id=?";
                //预编译sql
                psmt = conn.prepareStatement(sql);
                //设置参数
                psmt.setInt(1, 1);
                //执行sql
                rs = psmt.executeQuery();
                //遍历结果集
                while(rs.next())
                {
                    //返回得到的结果集
                    int id = rs.getInt("id");
                    String name = rs.getString("name");
                    String sex = rs.getString("sex");
                    //放到对象中
                    Student student = new Student();
                    student.setId(id);
                    student.setName(name);
                    student.setSex(sex);
                    System.out.println(student);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                if(rs != null)
                {
                    rs.close();
                }
                if(psmt != null)
                {
                    psmt.close();
                }
                if(conn != null)
                {
                    conn.close();
                }
            }
        }
    
    • 查询数量

    查询数量使用 queryForObject() 方法。

    @Test
        //查询数量操作
        public void queryCount()
        {
            //设置数据库信息
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
            dataSource.setUsername("root");
            dataSource.setPassword("root");
            
            //创建jdbcTemplate对象,设置数据源
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            
            //调用JdbcTemplate对象里面的方法实现操作
            String sql = "SELECT COUNT(id) FROM student";
            int count = jdbcTemplate.queryForObject(sql, Integer.class);
            System.out.println(count);
        }
    

    查询对象时,要先实现实体类的查询实现了 Spring 查询接口 RowMapper 的实现类。

    实体类的查询接口实现类

    package cc.wenshixin.jdbc;
    
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import org.springframework.jdbc.core.RowMapper;
    
    public class StudentRowMapper implements RowMapper<Student>{
    
        @Override
        public Student mapRow(ResultSet rs, int num) throws SQLException {
            //从结果集中得到数据
            int id = rs.getInt("id");
            String name = rs.getString("name");
            String sex = rs.getString("sex");
            
            //把得到的结果封装到对象里面
            Student student = new Student();
            student.setId(id);
            student.setName(name);
            student.setSex(sex);
            
            return student;
        }   
    }
    
    • 查询单个对象
        @Test
        //查询单个对象操作
        public void queryObject()
        {
            //设置数据库信息
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
            dataSource.setUsername("root");
            dataSource.setPassword("root");
            
            //创建jdbcTemplate对象,设置数据源
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            
            //调用JdbcTemplate对象里面的方法实现操作
            String sql = "SELECT * FROM student WHERE id=?";
            Student student = jdbcTemplate.queryForObject(sql, new StudentRowMapper(), 1);
            System.out.println(student.toString());
        }
    

    查询多个对象

        @Test
        //查询多个对象操作
        public void queryList()
        {
            //设置数据库信息
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
            dataSource.setUsername("root");
            dataSource.setPassword("root");
            
            //创建jdbcTemplate对象,设置数据源
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            
            //调用JdbcTemplate对象里面的方法实现操作
            String sql = "SELECT * FROM student";
            List<Student> studentList = jdbcTemplate.query(sql, new StudentRowMapper());
            System.out.println(studentList);
        }
    

    2.Spring 配置连接池


    在做 jdbc 操作时,不是每次都连接数据库,大多是创建一个数据库连接池,每次连接在这个数据库连接池中取连接对象,减少数据库连接和释放的操作。

    2.1 准备工作

    首先要导入配置 c3p0 连接池的 jar 包,c3p0.jarmchange.jar下载地址,然后创建 Spring 的配置文件。

    2.2 配置连接池

    以上面的 student 实体类为例,通过 service 类,dao 类,插入一条数据。

    配置文件

    属性注入数据库配置信息和 jdbcTemplate 的属性。

    <?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">
      <!-- 配置c3p0连接池 -->
      <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/spring?useSSL=false"></property>
        <property name="user" value=""></property>
        <property name="password" value=""></property>
      </bean>
      
      <!-- 配置创建对象 -->
      <bean id="studentService" class="cc.wenshixin.service.StudentService"></bean>
      <bean id="studentDao" class="cc.wenshixin.dao.StudentDao"></bean>
      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 把dataSource传递到模板对象里面 -->
        <property name="dataSource" ref="dataSource"></property>
      </bean>
      
      <!-- 开启注解扫描 -->
      <context:component-scan base-package="cc.wenshixin"></context:component-scan>
    </beans>
    

    DAO 类

    package cc.wenshixin.dao;
    
    import javax.annotation.Resource;
    
    import org.springframework.jdbc.core.JdbcTemplate;
    
    public class StudentDao {
        @Resource(name="jdbcTemplate")
        private JdbcTemplate jdbcTemplate;
        
        //插入数据
        public void add()
        {
            String sql = "INSERT INTO student(name,sex) VALUES(?,?)";
            jdbcTemplate.update(sql,"老王","男");
        }
    }
    

    Service 类

    package cc.wenshixin.service;
    
    import javax.annotation.Resource;
    
    import cc.wenshixin.dao.StudentDao;
    
    public class StudentService {
        @Resource(name="studentDao")
        private StudentDao studentDao;
        
        //添加操作
        public void add()
        {
            studentDao.add();
        }
    }
    

    测试类

    package cc.wenshixin.test;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import cc.wenshixin.service.StudentService;
    
    public class TestC3p0 {
        @Test
        public void test1()
        {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
            StudentService service = (StudentService) context.getBean("studentService");
            service.add();
        }
    }
    

    3.Spring 事务管理


    3.1 事务的基本概念

    事务是一组逻辑上的操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。

    事务的特性:

    • 原子性:强调事务是不可分割
    • 一致性:事务的执行的前后数据的完整性保持一致
    • 隔离性:一个事务执行的过程中,不应该收到其他事务的干扰
    • 持久性:事务一旦结束,数据就持久到数据库

    如果不考虑到隔离性引发的安全性问题:

    • 脏读:一个事务读到了另一个事务的未提交的数据
    • 不可重复读:一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致
    • 虚读:一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致

    解决读的问题:设置事务的隔离级别

    • 未提交读:脏读,不可重复读,虚读都有可能发生
    • 已提交读:避免脏读,但是不可重复读和虚读有可能发生
    • 可重复读:避免脏读和不可重复读,但是虚读有可能发生
    • 串行化:避免以上所有读的问题

    补充:Mysql 数据库默认的事务隔离级别为可重复读。

    3.2 Spring 事务管理的 API

    Spring 事务管理有两种方式,一种是编程式事务管理,不使用,另一种式声明式事务管理,基于xml配置文件实现和基于注解实现。

    Spring 平台事务管理器的接口 PlatformTransactionManager,针对不同的 DAO 层的框架提供了接口不同的实现类。

    事务管理器接口实现类 适用框架
    org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 Spring JDBC或IBatis进行持久化数据时使用
    org.springframework.orm.hibernate3.HibernateTransactionManager 使用 Hibernate5.x 版本进行持久化数据时使用

    下面通过一个银行转账的例子说明 Spring API 的使用

    创建数据表,并添加两条实验的数据

    初始数据.png

    DAO 类

    package cc.wenshixin.dao;
    
    import javax.annotation.Resource;
    
    import org.springframework.jdbc.core.JdbcTemplate;
    
    public class DealDao {
        @Resource(name="jdbcTemplate")
        private JdbcTemplate jdbcTemplate;
        
        public void subMoney(String name, int money)
        {
            String sql = "UPDATE yinhang SET money=money-? WHERE name=?";
            jdbcTemplate.update(sql, money, name);
        }
        
        public void addMoney(String name, int money)
        {
            String sql = "UPDATE yinhang SET money=money+? WHERE name=?";
            jdbcTemplate.update(sql, money, name);
        }
    }
    

    Service 类

    package cc.wenshixin.service;
    
    import javax.annotation.Resource;
    
    import cc.wenshixin.dao.DealDao;
    
    public class DealService {
        @Resource(name="dealDao")
        private DealDao dealDao;
        
        public void deal()
        {
            //小明转账给小黑1000元
            dealDao.subMoney("小明", 1000);
            dealDao.addMoney("小黑", 1000);
        }
    }
    

    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">
      <!-- 配置数据库信息 -->
      <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/spring?useSSL=false"></property>
        <property name="user" value=""></property>
        <property name="password" value=""></property>
      </bean>
      
      <!-- 配置对象 -->
      <bean id="dealService" class="cc.wenshixin.service.DealService"></bean>
      <bean id="dealDao" class="cc.wenshixin.dao.DealDao"></bean>
      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 把dataSource传递到模板对象里面 -->
        <property name="dataSource" ref="dataSource"></property>
      </bean>
      
      <!-- 开启注解扫描 -->
      <context:component-scan base-package="cc.wenshixin"></context:component-scan>
    </beans>
    

    测试类

    package cc.wenshixin.test;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import cc.wenshixin.service.DealService;
    
    public class DealTest {
        @Test
        public void test()
        {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
            DealService dealService = (DealService) context.getBean("dealService");
            dealService.deal();
        }
    }
    

    无异常产生,结果为正常

    无异常结果.png

    下面模拟异常情况的发生,实际情况为银行系统出错了,网络中断等等。

    在 Service 类中加入如下代码:

        //小明转账给小黑1000元
        dealDao.subMoney("小明", 1000);
        int i = 10 / 0; //模拟异常
        dealDao.addMoney("小黑", 1000);
    

    再次执行结果,出现除0的异常,但是数据库的结果为

    模拟异常结果.png

    小明少了1000元,但因为异常,小黑并没有增加1000元,这样肯定是不行的,出现异常需要进行事务回滚。

    将配置文件改成下面的配置文件,相比上面的配置文件,加入了事务的约束和事务管理器的配置,另外在要使用事务的方法所在类的上面加上 @Transactional,这个加不加都不影响结果,但是便于代码维护最好加上。

    <?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" 
        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/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
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
      <!-- 配置数据库信息 -->
      <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/spring?useSSL=false"></property>
        <property name="user" value=""></property>
        <property name="password" value=""></property>
      </bean>
      
      <!-- 配置对象 -->
      <bean id="dealService" class="cc.wenshixin.service.DealService"></bean>
      <bean id="dealDao" class="cc.wenshixin.dao.DealDao"></bean>
      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 把dataSource传递到模板对象里面 -->
        <property name="dataSource" ref="dataSource"></property>
      </bean>
      
      <!-- 开启注解扫描 -->
      <context:component-scan base-package="cc.wenshixin"></context:component-scan>
        
      <!-- 配置事务管理器 -->
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        注入dataSource
        <property name="dataSource" ref="dataSource"></property>
      </bean>
      
      <!-- 配置事务增强 -->
      <tx:advice id="txadvice" transaction-manager="transactionManager">
        <!-- 做事务操作 -->
        <tx:attributes>
          <!-- 设置进行事务操作的方法匹配规则 -->
          <tx:method name="deal*" propagation="REQUIRED"/>
        </tx:attributes>
      </tx:advice>
      
      <!-- 配置切面 -->
      <aop:config>
        <!-- 切入点 -->
        <aop:pointcut expression="execution(* cc.wenshixin.service.DealService.*(..))" id="pointcut"/>
        <!-- 切面 -->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pointcut"/>
      </aop:config>
    </beans>
    

    再次执行,出现异常,但是数据库的结果并不会有任何变化。

    相关文章

      网友评论

        本文标题:SSH框架之旅-spring(3)

        本文链接:https://www.haomeiwen.com/subject/umaxuxtx.html