通过前几章的学习,大家对 Spring 框架核心技术中的几个重要模块已经有了一定的了解,并且通过学习,相信大家也逐渐地体会到了使用 Spring 框架的好处。 Spring 框架降低了 Java EE API 的使用难度,其中就包括 JDBC 的使用难度 。JDBC 是 Spring 数据访问/集成中的重要模块,本章将对 Spring 中的 JDBC 知识进行详细讲解。
Spring JDBC
Spring 的 JDBC 模块负责数据库资源管理和错误处理,大大简化了开发人员对数据库的操作,使得开发人员可以从烦琐的数据库操作中解脱出来,从而将更多的精力投入到编写业务逻辑中。接下来的两个小节,将针对 Spring 中的 JDBC 模块内容进行详细的讲解。
- Spring JdbcTemplate 的解析
针对数据库的操作, Spring 框架提供了 JdbcTemplate 类,该类是 Spring 框架数据抽象层的基础,其他更高层次的抽象类却是构建于 JdbcTemplate 类之上。可以说, Jdbc Template 类是 Spring JDBC 的核心类。
Jdbc Template 类的继承关系十分简单。 它继承自抽象类JdbcAccessor ,同时实现了 JdbcOperations 接口,如图所示。
从图中可以看出, JdbcTemplate 类的直接父类是JdbcAccessor ,该类为子类提供了一些访问数据库时使用的公共属性,具体如下。
- DataSource: 其主要功能是获取数据库连接,具体实现时还可以引入对数据库连接的缓冲池和分布式事务的支持,它可以作为访问数据库资源的标准接口。
- SOLExceptionTranslator: org.springframework.jdbc.support.SOLExceptionT ranslator 接口负责对 SOLException 进行转译工作。 通过必要的设置或者获取 SOLExceptionTranslator 中的方法,可以使 JdbcTemplate 在需要处理 SOLException 时,委托 SOLExceptionTranslator 的实现类来完成相关的转译工作。
JdbcOperations 接口定义了在 JdbcTemplate 类中可以使用的操作集合,包括添加、修改、查询和删除等操作。
- Spring JDBC 的配置
Spring JDBC 模块主要由 4 个包组成,分别是 core (核心包)、 dataSource (数据源包)、object (对象包)和support (支持包),关于这 4 个包的具体说明如表所示。
包名 说明 core 包含了 JDBC 的核心功能,包括 JdbcTemplate 类、 SimpleJdbclnsert 类、 SimpleJdbcCal1类以及 NamedParameterJdbcTemplate类 dataSource 访问数据源的实用工具类,白有多种数据源的实现,可以在 Java EE 容器外部测试 JDBC 代码 object 以面向对象的方式访问数据库, 它允许执行查询并将返回结果作为业务对象,可以在数据袤的列和业务对象的属性之间映射查询结果 support 包含了 core 和 object 包的支持类,例如,提供异常转换功能的 SQLException类 从表中可以看出, Spring 对数据库的操作都封装在了这几个包中,而想要使用 Spring JDBC ,就需要对其进行配置。在 Spring 中, JDBC 配置是在配置文件applicationContext. 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/spriηg-beans-4.3.xsd"> <!-- 1.配置数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <!-- 数据库驱动 --> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <!-- 连接数据库的 url --> <property name="url" value="jdbc:mysql://localhost:3306/spring" /> <!-- 连接数据库的用户名 --> <property name="username" value="root" /> <!-- 连接数据库的密码 --> <property name="password" value="root" /> </bean> <!-- 2.配置 JDBC 模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 默认必须使用数据源 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 3.配置注入类 --> <bean id="xxx" class="Xxx"> <property name="jdbcTemplate" ref="jdbcTemplate" /> </bean> </beans>
在上述代码中,定义了3个 Bean ,分别是 dataSource、jdbcTemplate 和需要注入类的 Bean。其中 dataSource 对应的 org.springframework.jdbc.datasource.DriverManagerDataSource类用于对数据源进行配置, jdbcTemplate 对应的 org.springframework.jdbc.core.JdbcTemplate类中定义了 JdbcTemplate 的相关配置。 上述代码中 dataSource 的配置就是 JDBC 连接数据库时所需的 4个属性,如表所示。
表中的 4个属性,需要根据数据库类型或者机器配置的不同设置相应的属性值。 例如,如果数据库类型不同,需要更改驱动名称;如果数据库不在本地,则需要将地址中的 localhost替换成相应的主机IP ;如果修改过 MySQL 数据库的端口号 (默认为 3306 ),则需要加上修改后的端口号,如果未修改,则端口号可以省略;同时连接数据库的用户名和密码需要与数据库创建时设置的用户名和密码保持一致,本示例中 Spring 数据库的用户名和密码都是 root。
属性名 含义 driverClassName 所使用的驱动名称,对应驱动 JAR 包中的 Driver类 url 数据源所在地址 username 访问数据库的用户名 password 访问数据库的密码 定义 jdbcTemplate 时,需要将 dataSource 注入到 jdbcTemplate 中,而其他需要使用jdbcTemplate 的 Bean ,也需要将 jdbcTemplate 注入到该 Bean 中(通常注入到 Dao 类中,在Dao 类中进行与数据库的相关操作)。
Spring JdbcTemplate 的常用方法
在 JdbcTemplate 类中,提供了大量的更新和查询数据库的方法,我们就是使用这些方法来操作数据库的。 接下来的几个小节中,将对 JdbcTemplate 类中一些常用方法的使用进行详细讲解。
- execute()
execute(String sql) 方法能够完成执行 SOL 语句的功能 。下面以创建数据表的 SOL 语句为例,来演示此方法的使用,具体步骤如下。
( 1 )在 MySOL 中,创建一个名为 spring 的数据库,创建方式如图所示。
在图上中,首先创建了数据库 spring ,然后选择使用 spring。 为了便于后续验证数据表是通过 execute ( String sql )方法执行创建的,这里查看数据库中的表,其结果显示为空。
( 2 )在 Eclipse 中,创建一个名为 spring04 的 Web 项目,将运行 Spring 框架所需的 5 个基础 JAR 包、 MySOL数据库的驱动 JAR 包、 Spring JDBC 的 JAR 包以及 Spring事务处理的 JAR 包复制到项目的 lib 目录,并发布到类路径中。项目中所添加的 JAR 包如图所示。
( 3 )在 src 目录下,创建配置文件 applicationContext.xml ,在该文件中配置 id 为 dataSource的数据源 Bean 和 id 为 jdbcTemplate 的 JDBC 模板 Bean ,并将数据源注入到 JDBC 模板中,文件如下所示。<?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/spriηg-beans-4.3.xsd"> <!-- 1.配置数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <!-- 数据库驱动 --> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <!-- 连接数据库的 url --> <property name="url" value="jdbc:mysql://localhost:3306/spring" /> <!-- 连接数据库的用户名 --> <property name="username" value="root" /> <!-- 连接数据库的密码 --> <property name="password" value="root" /> </bean> <!-- 2.配置 JDBC 模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 默认必须使用数据源 --> <property name="dataSource" ref="dataSource" /> </bean> </beans>
(4 )在 src 目录下,创建一个 com.neuedu.jdbc 包,在该包中创建测试类 JdbcTemplateTest。在该类的 main() 方法中通过 Spring 容器获取在配置文件中定义的 JdbcTemplate 实例,然后使用该实例的 execute(String sql) 方法执行创建数据表的 SQL 语句,文件如下所示。
package com.neuedu.jdbc; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; public class JdbcTemplateTest { /** * 使用execute()方法建表 */ public static void main(String[] args) { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取JdbcTemplate实例 JdbcTemplate jdbcTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate"); // 使用execute()方法执行SQL语句,创建用户账户表account jdbcTemplate.execute("create table account(" + "id int primary key auto_increment," + "username varchar(50) ," + "balance double)"); System.out.println("账户表account创建成功!"); } }
成功运行程序后,再次查询 spring 数据库,其结果如图所示。
从图中可以看出,程序使用 execute ( String sql )方法执行的 SQL 语句已成功创建了数据表 account。
- 多学一招:使用 JUnit 测试
在软件开发过程中,需要有相应的测试工作。 依据测试目的不同,可以将软件测试分为单元测试、集成测试、确认测试和系统测试等。其中单元测试在软件开发阶段是最底层的测试,它易于及时发现并解决问题。 JUnit 就是一个进行单元测试的开源框架,下面以上述文件为例,来简单学习一下单元测试框架 JUnit4 的使用。
将上述文件的 main() 方法, 修改成名称为 mainTest() 的普通方法,并在方法上添加单元测试的注解@Test ,其代码如下所示。@Test public void mainTest() { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取JdbcTemplate实例 JdbcTemplate jdbcTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate"); // 使用execute()方法执行SQL语句,创建用户账户表account jdbcTemplate.execute("create table account(" + "id int primary key auto_increment," + "username varchar(50) ," + "balance double)"); System.out.println("账户表account创建成功!"); }
@Test 就是 Junit4 用于测试的注解 ,要测试哪个方法,只需要在相应测试的方法上添加此注解即可。当在需要测试的方法上添加@Test 注解后, Eclipse 会在所添加的@Test 处报出 Test cannot be resolved to a type 的错误,将鼠标移到@Test 处,会显示出错误提示柜如图所示。单击提示柜中的 Add JUnit4 library to the build path Eclipse 会自动将只Jnit4 的支持包加入到项目中,如图 所示。
加入后,在测试类中会自动导入 org.junit.Test包, 此时测试类中的代码将不再报错。在执行程序时,只需使用鼠标右键单击 mainTest()方法,在弹出的快捷菜单中选择 Run As ->JUnit Test 选项来运行测试方法即可,如图所示。
单击 JUnit Test 选项后, Eclipse 中会多出一个名为 JUnit 的视图窗口,其显示结果如图所示。
在图中, JUnit 视图窗口的进皮条为绿色表明运行结果正确,如果进度条为红色则表示有错误,并且会在窗口中显示所报的错误信息。
需要注意的是,在运行此方法时,需要先将数据库中已创建好的 account 表删除,否则执行此方法时会报出account 表已经存在的错误。
测试执行通过后, Console 控制台的输出结果如图所示。
从图中可以看出, mainTest() 方法已经执行成功,这就是单元测试的使用。
- update()
update() 方法可以完成插入、更新和删除数据的操作 。在 JdbcTemplate 类中,提供了一系列的 update() 方法,其常用方法如下表所示。
方法 说明 int update(String sql) 该方法是最简单的 update 方法重载形式,它直接执行传入的SQL语句,并返回受影响的行数 int update(PreparedStatementCreator psc) 该方法执行从 PreparedStatementCreator 返回的语句,然后返回受影响的行数 int update(String sql,PreparedStatementSetter pss) 该方法通过 PreparedStatementSetter 设置 SQL 语句中的参数,并返回受影响的行数 int update(String sql,Object... args) 该方法使用Object...设置 SQL 语句中的参数,要求参数不能为NULL ,并返回受影响的行数 接下来,通过一个用户账户管理的案例来演示 update() 方法的使用,具体步骤如下。
( 1)在 spring04 项目的 com.neuedu.jdbc 包中,创建 Account 类,在该类中定义 id、username 和 balance 属性 ,以及其对应的 getter/setter 方法,文件如下所示。package com.neuedu.jdbc; public class Account { private Integer id;//账户ID private String username;//账户名 private double balance;//账户余额 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } @Override public String toString() { return "Account [id=" + id + ", username=" + username + ", balance=" + balance + "]"; } }
( 2)在 com.neuedu.jdbc 包中,创建接口 AccountDao, 并在接口中定义添加、更新和删除账户的方法 ,文件如下所示。
package com.neuedu.jdbc; public interface AccountDao { //添加 public int addAccount(Account account) ; //更新 public int updateAccount (Account account) ; //删除 public int deleteAccount (int id); }
( 3)在 com.neuedu.jdbc 中, 创建 Account 接口的实现类 AccountDaoImpl ,并在类中实现添加、更新和删除账户的方法 编辑后文件如下所示。
package com.neuedu.jdbc; import org.springframework.jdbc.core.JdbcTemplate; public class AccountDaoImpl implements AccountDao { //声明 JdbcTemplate 属性及其 setter 方法 private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } //增加账户 public int addAccount(Account account) { //定义SQL String sql = "insert into account(username,balance) value (?,?) "; //定义数组来存储 SQL 语句中的参数 Object[] obj = new Object[]{ account.getUsername(), account.getBalance() }; //执行添加操作,返回的是受 SQL语句影响的记录条数 int num = this.jdbcTemplate.update(sql,obj); return num; } //更新账户 public int updateAccount(Account account) { //定义SQL String sql = "update account set username=? ,balance=? where id = ?"; //定义数组来存储 SQL 语句中的参数 Object[] params = new Object[]{ account.getUsername(), account.getBalance(), account.getId() }; //执行更新操作,返回的是受 SQL 语句影响的记录条数 int num = this.jdbcTemplate.update(sql,params); return num; } //删除账户 public int deleteAccount(int id) { //定义SQL String sql = "delete from account where id = ?"; //执行删除操作,返回的是受 SQL语句影响的记录条数 int num = this.jdbcTemplate.update(sql,id); return num; } }
从上述三种操作的代码可以看出,添加、更新和删除操作的实现步骤类似,只是定义的 SQL语句有所不同。
( 4)在 applicationContext.xml 中,定义一个 id 为 accountDao 的 Bean,该 Bean 用于将 jdbcTemplate 注入到 accountDao 实例中,其代码如下所示。<!-- 定义一个 id 为 accountDao的 Bean --> <bean id="accountDao" class="com.neuedu.jdbc.AccountDaoImpl"> <!-- 将 jdbcTernplate 注入到 accountDao 实例中 --> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean>
( 5)在测试类 JdbcTemplateTest 中,添加一个测试方法 addAccountTest(), 该方法主要用于添加用户账户信息,其代码如下所示。
@Test public void addAccountTest() { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取AccountDao 实例 AccountDao accountDao = (AccountDao)applicationContext.getBean("accountDao"); //创建 Account 对象,并向 Account 对象中添加数据 Account account = new Account(); account.setUsername("zhaosi"); account.setBalance(1000.00); //执行addAccount()方法,并获取返回结果 int num = accountDao.addAccount(account); if(num > 0){ System.out.println("成功添加了" + num + "条数据!"); }else{ System.out.println("添加操作执行失败!"); } }
在上述代码中,获取了 AccountDao 的实例后,又创建了 Account 对象,并向 Account 对象中添加了属性值。 然后调用了 AccountDao 对象的 addAccount() 方法向数据表中添加一条数据。最后,通过返回的受影响的行数来判断数据是否插入成功。
使用 Junit4 测试运行后,控制台的输出结果如图所示。
此时再次查询数据库中的 account 表,其结果如图所示。
从图中可以看出,使用 JdbcTemplate的 update() 方法已成功地向数据表中插入了一条数据。
( 6 )执行完插入操作后,接下来使用 JdbcTemplate 类的 update() 方法执行更新操作。 在测试类 JdbcTemplateTest 中,添加一个测试方法 updateAccountTest() ,其代码如下所示。@Test public void updateAccountTest() { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取AccountDao 实例 AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao"); // 创建 Account 对象,并向 Account 对象中添加数据 Account account = new Account(); account.setId(1); account.setUsername("zhaosi"); account.setBalance(2000.00); // 执行updateAccount方法,并获取返回结果 int num = accountDao.updateAccount(account); if (num > 0) { System.out.println("成功修改了" + num + "条数据!"); } else { System.out.println("修改操作执行失败!"); } }
与 addAccountTest() 方法相比,更新操作的代码增加了 id 属性值的设置,并将余额修改为2000 后,调用了 AccountDao 对象中的 updateAccount() 方法执行对数据表的更新操作。
使用 Junit4 运行方法后,再次查询数据库中的 account表,其结果如图所示。
从图中可以看出,使用 update() 方法已成功更新了 account 表中 id 为 1 的账户余额信息。
( 7) 在测试类 JdbcTemplateTest 中,添加一个测试方 deleteAccountTest(),来执行删除操作,其代码如下所示。@Test public void deleteAccountTest() { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取AccountDao 实例 AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao"); //执行deleteAccount()方法,并获取返回值 int num = accountDao.deleteAccount(1); if (num > 0) { System.out.println("成功删除了" + num + "条数据!"); } else { System.out.println("删除操作执行失败!"); } }
在上述代码中,获取了 AccountDao 的实例后,执行了实例中的 deleteAccount() 方法来删除 id 为 1 的数据。
使用 Junit4 测试运行方法后,查询 account 表中数据,其结果如图所示。
从图中可以看出,已成功通过 update() 方法删除了 id 为 1 的数据。 由于 account 表中只有一条数据,所以删除后表中数据为空。
- query()
JdbcTemplate 类中还提供了大量的 query() 方法来处理各种对数据库表的查询操作。 其中,常用的几个 query() 方法如下表所示。
方法 说明 List query(String sql, RowMapper rowMapper) 执行 String 类型参数提供的 SQL 语句,并通过RowMapper 返回一个List 类型的结果 List query (String sql, PreparedStatementSetter pss,RowMapper rowMapper ) 根据String 类型参数提供的 SQL 语句创PreparedStatement 对象,通过RowMapper 将结果返回到List中 List query ( String sql, ObjectD args, RowMapper rowMapper) 使用 Object口的值来设置 SQL 语句中的参数值,采用 RowMapper 回调方法可以直接返回List 类型的数据 queryForObject(String sql, RowMapper rowMapper,Object. .. args) 将args 参数绑定到 SQL 语句中,并通过 RowMapper返回一个 Object 类型的单行记录 queryForList ( String sql,Object[] args, class<T> elementType) 该方法可以返回多行数据的结果,但必须是返回列表, elementType 参数返回的是List 元素类型 了解了几个常用的 query() 方法后,接下来通过一个具体的案例来演示 query() 方法的使用,其实现步骤如下。
( 1 )向 数据表 account 中插入几条数据 (也可以使用数据库图形化工具手动向表中插入数据),插入后 account 表中的数据如图所示。
( 2 )在 AccountDao 中, 分别创建一个通过 id 查询单个账户和查询所有账户的方法,其代码如下所示。
//通过id查询 public Account findAccountByld (int id) ; //查询所有账户 public List<Account> findAllAccount();
( 3 )在 AccountDao 接口的实现类 AccountDaolmpl 中,实现接口中的方法,并使用 query()方法分别进行查询,其代码如下所示。
// 通过 id查询账户数据信息 public Account findAccountByld(int id) { // 定义SQL语句 String sql = "select * from account where id = ? "; // 创建一个新的 BeanPropertyRowMapper 对象 RowMapper<Account> roWMapper = new BeanPropertyRowMapper<Account>(Account.class); // 将 id 绑定到 SQL语句中,井通过 RowMapper 返回一个 Object 类型的单行记录 return this.jdbcTemplate.queryForObject(sql, roWMapper, id); } // 查询所有账户信息 public List<Account> findAllAccount() { // 定义SQL语句 String sql = "select * from account"; // 创建一个新的 BeanPropertyRowMapper 对象 RowMapper<Account> roWMapper = new BeanPropertyRowMapper<Account>(Account.class); ///执行静态的 SQL 查询,并通过 RowMapper 返回结果 return this.jdbcTemplate.query(sql, roWMapper); }
在上面两个方法代码中, BeanPropertyRowMapper 是RowMapper 接口的实现类,它可以自动地将数据表中的数据映射到用户自定义的类中(前提是用户自定义类中的字段要与数据表中的字段相对应) 。创建完BeanPropertyRowMapper 对象后,在 findAccountByld() 方法中通过queryForObject() 方法返回了一个 Object 类型的单行记录,而在 findAllAccount() 方法中通过query() 方法返回了一个结果集合。
( 4 )在测试类 JdbcTemplateTest 中,添加一个测试方 findAccountByIdTest() 来测试条件查询,其代码如下所示。@Test public void findAccountByIdTest() { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取AccountDao 实例 AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao"); // 执行findAccountById()方法 Account account = accountDao.findAccountByld(2); System.out.println(account); }
上述代码通过执行 findAccountByld() 方法获取了 id 为 2 的对象信息,并通过输出语句输出。
使用 JUnit4 测试运行后,控制台的输出结果如图所示。
( 5 )测试完条件查询单个数据的方法后,接下来测试查询所有用户账户信息的方法 在测试JdbcTemplateTest 中,添加一个测试方法 findAllAccountTest(),其代码如下所示。
@Test public void findAIIAccountTest() { // 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取AccountDao 实例 AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao"); // 执行findAIIAccount()方法,获取account的对象集合 List<Account> account = accountDao.findAllAccount(); //循环输出集合中的对象 for (Account acc : account) { System.out.println(acc); } }
在上述代码中,调用了 AccountDao 对象的 findAIIAccount() 方法查询所有用户账户信息集合,并通过 for 循环输出查询结果。
使用 JUnit4 成功运行 findAllUserTest() 方法后,控制台的显示信息如图所示。
从图中可以看出,数据表 account 中的 4 条记录都已经被查询出来。
本章小结
本章对 Spring 框架中使用 JDBC 进行数据操作的知识进行了详细讲解。 首先讲解了 Spring JDBC 中的核心类以及如何在 Spring 中配置 JDBC ,然后通过案例讲解了 Spring JDBC 核心类 JdbcTemplate 中常用方法的使用。 通过本章的学习,大家能够学会如何使用 Spring 框架进行数据库开发,并能深切地体会到 Spring 框架的强大。