Spring 学习其三:数据库编程
一、直接利用 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 进行数据持久化工作,有下面几个步骤
- 根据配置文件建立一个 sqlSessionFactory,在配置文件里,我们定义了数据库连接信息以及 mapper 文件的位置,MyBatis 会自动加载它们。
- 利用 sqlSessionFactory 创建一个 session。
- 进行数据操作
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 数据库事务管理