一、直接利用 JDBC 进行数据库编程

虽然有 Spring 和 MyBatis 等数据持久化的利器在手,但还是有必要去了解下传统的 JDBC 开发方式。无论哪种框架,其本质都是在传统的 JDBC 方式上进行了封装。



package JdbcTest;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcTest {

    public static void main(String[] args) {
        EmployeeModel employee = null;    
        Connection con = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            con = DriverManager.getConnection("jdbc:mysql://192.168.52.129:3306/employees?serverTimezone=UTC", "root", "Root123#");
            ps =con.prepareStatement("select emp_no,first_name,last_name from employees where emp_no = ?");
            ps.setInt(1, 10001);
            rs = ps.executeQuery();
            while(rs.next()) {
                employee = new EmployeeModel();
                employee.setEmpNo(rs.getInt(1));
                employee.setFirstName(rs.getString(2));
                employee.setLastName(rs.getString(3));
            }
        } catch (ClassNotFoundException e) {
            System.out.println("class not found");
            } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            try {
                if(!(rs == null) && !rs.isClosed()) {
                    rs.close();
                }
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            try {
                if(!(ps == null) && ps.isClosed()) {
                    ps.close();
                }
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            try {
                if(con !=null && !con.isClosed()) {
                    con.close();
                }
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println(employee);
    }

}



这段代码的恼人的地方在于数据库资源的使用和销毁,这些代码被大串的 try-catch 语句包裹。Spring 则为我们进行了一次包装,将这些烦人的 try-catch 语句交给 Spring 去处理。

二、为 Spring 创建第三方数据库连接池

Spring 本身也有自己的数据类,但是过于简单,在绝大多数的项目中,我们都希望能够用数据连接池的方式去管理连接。常用的第三方连接池是 DBCP2。

在配置 DBCP2 之前,现在 properties 文件里存好数据库连接所需的信息:



database.driver = com.mysql.cj.jdbc.Driver
database.url = jdbc:mysql://192.168.52.129:3306/employees?serverTimezone=UTC
database.username = root
database.password = Root123#
database.maxtotal = 255
database.maxidle = 3
database.maxwaitmillis = 10000



maxtoal 值最大连接数,maxidle 为最大等待连接数量(超出这个数量的连接资源会被释放),maxwaitmillis 为最大等待毫秒数

然后再 spring-cfg.xml 里引入该 peoperties 文件并且建立一个连接池对象:



<bean id = "dataSource" class = "org.apache.commons.dbcp2.BasicDataSource">
      <property name = "driverClassName" value = "${database.driver}"/>
      <property name="url" value="${database.url}"/>
      <property name="username" value="${database.username}"/>
      <property name="password" value="${database.password}"/>
      <property name="maxTotal" value="${database.maxtotal}"/>
      <property name="maxIdle" value="${database.maxidle}"/>
      <property name="maxWaitMillis" value="${database.maxwaitmillis}"/>
    </bean>



而包装传统 jdbc 的 Spring 的类为 JdbcTemple ,我们也配置下:



<bean id="jdbcTemplate" class = "org.springframework.jdbc.core.JdbcTemplate">
      <property name = "dataSource" ref = "dataSource"/>
    </bean>



然后使用 JdbcTemplate 来完成上面的获取 employee 信息的功能:

 



import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
public class JdbcTes2 {
    public static void main(String args[]) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
        JdbcTemplate jp = ctx.getBean(JdbcTemplate.class);
        int id = 10001;
        String sql = "select emp_no,first_name,last_name from employees where emp_no = " + id;
        EmployeeModel employee = jp.queryForObject(sql, new RowMapper<EmployeeModel>() {

            @Override
            public EmployeeModel mapRow(ResultSet rs, int rowNum) throws SQLException {
                EmployeeModel employee = new EmployeeModel();
                employee.setEmpNo(rs.getInt("emp_no"));
                employee.setFirstName(rs.getString("first_name"));
                employee.setLastName(rs.getString("last_name"));
                return employee;
            }
            
        });
        System.out.println(employee);
    }
}



JdbcTemplate 是 Spring 自己用来处理数据库的工具类,它很简单,甚至没有实现事务管理相关的功能。

三、MyBatis-Spring 

Spring + MyBatis 是目前非常常见的组合,为此,MyBatis 也专门为 Spring 设立了新的项目,也就是 MyBatis-Spring 项目。它最大的作用就是让我们可以利用 spring 配置 SqlFactory。

先来回顾下 mybatis 的使用代码:



public static void main(String[] args) {
        SqlSessionFactory sqlSessionFactory = null;
        String resource = "mybatis-config.xml";
        InputStream inputStream;
        try {
            inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }catch(IOException e) {
            e.printStackTrace();
        }
        SqlSession sqlSession = sqlSessionFactory.openSession();
        Role role = (Role)sqlSession.selectOne("mybatisTest.mapper.RoleMapper.getRole",1);
        System.out.println(role.getAge());
    }



想要使用 MyBatis 进行数据持久化工作,有下面几个步骤

  1. 根据配置文件建立一个 sqlSessionFactory,在配置文件里,我们定义了数据库连接信息以及 mapper 文件的位置,MyBatis 会自动加载它们。
  2. 利用 sqlSessionFactory 创建一个 session。
  3. 进行数据操作

sqlSessionFactory 在整个项目中只需要一个就够了,我们当希望能在 Spring 里注册一个 sqlSessionFactory 的 java bean ,为了方便我们注册这个 java bean。mybatis 为我们专门提供了一个 SqlSessionFactory。它可以自己根据地址去加载 xml 文件,然后生成一个 SqlSessionFactory。



<bean id = "sqlSessionFactoryBean" class = "org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref = "dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations">
      <list>
        <value>classpath*:com/**/*Mapper.xml</value>
      </list>
    </property>
  </bean>



configLocation 属性就是其配置文件的地址。

mapperLocations :我们为了使用 Mapper 需要在 mybatis-config 里去装载,利用 SqSessionFactoryBean 的 mapperLocations 属性,我们可以加载多个 mapper 文件。甚至,由于 spring 支持通配符,我们可以把所有的 mapper 用简单的正则表达式加载进来(mybatis 本身不支持通配符);

我们还可以进一步简化我们的代码。

在创建了 SqlFactory 之后,我们还需要获取 Dao 对应的实现对象,也就是代码中的:

SqlSession sqlSession = sqlSessionFactory.openSession();

Role role = (Role)sqlSession.selectOne("mybatisTest.mapper.RoleMapper.getRole",1);

能不能把这个步骤也省略了?这就需要用到另一个工具 MapperScannerConfigurer。这个类会自动扫描我们的 dao 类,然后自动为他们绑定生成的类:

它的配置如下:



<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.**.dao" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
    <property name="annotationClass" value="org.springframework.stereotype.Repository"/>
  </bean>



它会从我们指定的 basePackage 里去找 dao 类,然后利用我们制定的 sqlSessionFactoryBean 生成一个 sqlSession 然后利用这个 sqlsession 去执行我们通过 mapper 文件配置的数据库语句。其实可以理解为,它会为我们的 dao 生成对应的 daoImpl 类,并且注册成 java bean,然后我们可以通过自动注入的方式去获取这个实现类。

它的 annotationClass 表示它只会针对我们制定的注释的 dao 类进行操作,dao 类一般用 Repository 注释。

现在我们的 spring-cfg.xml 文件全部内容如下:



<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:cache="http://www.springframework.org/schema/cache" 
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
     http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-4.0.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
     http://www.springframework.org/schema/cache 
     http://www.springframework.org/schema/cache/spring-cache-4.0.xsd">
    <bean class = "org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
      <property name = "locations">
        <list>
          <value>classpath:jdbc.properties</value>
          <value>classpath:log4j2.properties</value>
        </list>
      </property>
       <property name = "ignoreResourceNotFound" value = "true"/>
    </bean>
    <bean id = "dataSource" class = "org.apache.commons.dbcp2.BasicDataSource">
      <property name = "driverClassName" value = "${database.driver}"/>
      <property name="url" value="${database.url}"/>
      <property name="username" value="${database.username}"/>
      <property name="password" value="${database.password}"/>
      <property name="maxTotal" value="${database.maxtotal}"/>
      <property name="maxIdle" value="${database.maxidle}"/>
      <property name="maxWaitMillis" value="${database.maxwaitmillis}"/>
    </bean>
    
    <bean id = "sqlSessionFactoryBean" class = "org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref = "dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations">
      <list>
        <value>classpath*:SpringTest/**/*Mapper.xml</value>
      </list>
    </property>
  </bean>
  <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="SpringTest.**.dao" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
    <property name="annotationClass" value="org.springframework.stereotype.Repository"/>
  </bean>
  
    <bean id="jdbcTemplate" class = "org.springframework.jdbc.core.JdbcTemplate">
      <property name = "dataSource" ref = "dataSource"/>
    </bean>
    <bean id = "performAspect" class = "SpringTest.performance.aspect.Audience"/>
    <bean id = "performer" class = "SpringTest.performance.service.impl.PerformerImpl">
    </bean>
</beans>



然后如何使用?



public class EmployeeTest {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
        EmployeeDao employeeDao = ctx.getBean(EmployeeDao.class);
        System.out.println(employeeDao.getEmployeeByEmpNo(10001));
    }

}



通过 MyBatis-Spring 将很多重复的工作一次性在加载 IOC 容器的时候直接完成了。

需要注意,必须在 dao 类上方加上 @Repository 注释,或者去掉 MapperScannerConfigurer 里的 annotationClass  属性。

 四、Spring 数据库事务管理