Spring 为数据库编程提供了 JDBC 模板,允许简化许多代码,但实际中并不常用,因此只做了解。
全部章节传送门:
Spring学习笔记(一):Spring IoC 容器
Spring学习笔记(二):Spring Bean 装配
Spring学习笔记(三): Spring 面向切面
Spring学习笔记(四): Spring 数据库编程
Spring学习笔记(五): Spring 事务管理
传统 JDBC 代码
首先总结一下传统 JDBC 操作。
为了测试数据库操作,首先在数据库中插入一个表,并添加数据。
create table t_role(
id int auto_increment primary key,
role_name varchar(20),
note varchar(50)
);
insert into t_role(role_name, note) values('warrior','Warriors can use all kinds of weapons skillfully');
传统 JDBC 回顾
创建 MAVEN 项目并添加 JDBC 依赖。
mysql
mysql-connector-java
5.1.25
创建和数据库中的表相对应的 Role 实体类。
package com.wyk.springdb.domain;
public class Role {
private Long id;
private String roleName;
private String note;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
然后编写一段传统的 JDBC 查询代码。
package com.wyk.springdb.db;
import com.wyk.springdb.domain.Role;
import java.sql.*;
public class DbOPeration {
public Role getRole(Long id) {
Role role = null;
//声明jdbc变量
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//注册驱动程序
Class.forName("com.mysql.jdbc.Driver");
//获取连接
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/springstudy",
"root", "wanyunkai123");
//预编译 SQL
ps = con.prepareStatement("select id, role_name, note from t_role where id = ?");
//设置参数
ps.setLong(1, id);
//执行SQL
rs = ps.executeQuery();
//组合结果返回POJO
while(rs.next()) {
role = new Role();
role.setId(rs.getLong(1));
role.setRoleName(rs.getString(2));
role.setNote(rs.getString(3));
}
} catch (ClassNotFoundException | SQLException e) {
//异常处理
e.printStackTrace();
} finally {
//关闭数据库连接资源
try {
if(rs != null && (!rs.isClosed())) {
rs.close();
}
} catch(SQLException e) {
e.printStackTrace();
}
try {
if(ps != null && !ps.isClosed()) {
ps.close();
}
} catch(SQLException e) {
e.printStackTrace();
}
try {
if(con != null && !con.isClosed()) {
con.close();
}
} catch(SQLException e) {
e.printStackTrace();
}
}
return role;
}
}
然后编写代码进行测试。
public class JdbcTest {
public static void main(String[] args) {
DbOPeration operation = new DbOPeration();
Role role = operation.getRole(1L);
System.out.println("{id: " + role.getId() + ", role_name: "
+ role.getRoleName() + ", note: " + role.getNote() + "}");
}
}
运行程序可以查询出结果,但是可以看出仅仅是一条简单的查询,代码也很复杂,其中也包含较多的 try...catch...finally... 语句。
优化传统 JDBC
我们可以把重复的模板代码提出来创建一个工具类。
package com.wyk.springdb.util;
import org.omg.CORBA.OBJECT_NOT_EXIST;
import java.sql.*;
public class DBUtil {
static String ip = "127.0.0.1";
static int port = 3306;
static String database = "springstudy";
static String encoding = "UTF-8";
static String username = "root";
static String password = "wanyunkai123";
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//建立连接
public static Connection getConnection() throws SQLException {
String url = String.format("jdbc:mysql://%s:%d/%s?characterEncoding=%s",
ip, port, database, encoding);
return DriverManager.getConnection(url, username, password);
}
public static void closeResource(Connection conn, Statement st, ResultSet rs) {
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
这样就可以修改查询类。
package com.wyk.springdb.db;
import com.wyk.springdb.domain.Role;
import com.wyk.springdb.util.DBUtil;
import java.sql.*;
public class DbOPeration2 {
public Role getRole(Long id) {
Role role = null;
//声明jdbc变量
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
con = DBUtil.getConnection();
//预编译 SQL
ps = con.prepareStatement("select id, role_name, note from t_role where id = ?");
//设置参数
ps.setLong(1, id);
//执行SQL
rs = ps.executeQuery();
//组合结果返回POJO
while(rs.next()) {
role = new Role();
role.setId(rs.getLong(1));
role.setRoleName(rs.getString(2));
role.setNote(rs.getString(3));
}
} catch (SQLException e) {
//异常处理
e.printStackTrace();
} finally {
DBUtil.closeResource(con, ps, rs);
}
return role;
}
}
尽管可以优化一下,但我们会发现传统的 JDBC 还是过于复杂。
配置数据库资源
为了解决 JDBC 的问题, Spring 提供了自己的解决方案,那就是 JdbcTemplate 模板,不过首先需要了解一下如何配置数据库资源。
简单数据库配置
Spring 提供了一个简单的数据库配置 org.springframework.jdbc.datasource.SimpleDriverDataSource,它很简单,不支持数据库连接池,一般用于简单的测试。
### 使用第三方数据库连接池
正式开发中经常使用第三方的数据库连接池,比如 DBCP 数据库连接池。
```xml
使用 JNDI 数据库连接池
当数据源配置在 Tomcat 等服务器上的时候,需要通过 JNDI 配置数据源。假设在 Tomcat 上配置了 JNDI 为 jdbc/springdb 的数据源,则 Web 工程中配置如下。
JdbcTemplate
JdbcTemplate 是 Spring 针对 JDBC 代码失控提供的解决方案,但严格来说,它本身不算成功。但体现了 Spring 框架的主导思想之一: 给与常用技术提供模板化编程,减少开发者工作量。
首先为项目添加完整的配置文件 spring-cfg.xml。其中使用了 DBCP 数据库连接池,需添加 DBCP 依赖。
然后添加数据库操作的测试类。
package com.wyk.springdb.test;
import com.mysql.jdbc.JDBC4CallableStatement;
import com.wyk.springdb.domain.Role;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcTempLateTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
JdbcTemplate jdbcTemplate = ctx.getBean(JdbcTemplate.class);
Long id = 1L;
String sql = "select id, role_name, note from t_role where id = " + id;
Role role = jdbcTemplate.queryForObject(sql, new RowMapper() {
public Role mapRow(ResultSet rs, int i) throws SQLException {
Role result = new Role();
result.setId(rs.getLong("id"));
result.setRoleName(rs.getString("role_name"));
result.setNote(rs.getString("note"));
return result;
}
});
System.out.println("{id: " + role.getId() + ", role_name: "
+ role.getRoleName() + ", note: " + role.getNote() + "}");
}
}
查询成功,其中代码里使用了匿名类, Java 8 也可以使用 Lambda 表达式。
增、删、改、查都可以使用JdbcTemplate。
public class JdbcOperation {
public int insertRole(JdbcTemplate jdbcTemplate) {
String roleName = "role_name_1";
String note = "note_1";
String sql = "insert into t_role(role_name, note) values(?, ?)";
return jdbcTemplate.update(sql, roleName, note);
}
public int deleteRole(JdbcTemplate jdbcTemplate, Long id) {
String sql = "delete from t_role where id=?";
return jdbcTemplate.update(sql, id);
}
public int updateRole(JdbcTemplate jdbcTemplate, Role role) {
String sql = "update t_role set role_name=?, note=? where id=?";
return jdbcTemplate.update(sql, role.getRoleName(), role.getNote(), role.getId());
}
public List findRole(JdbcTemplate jdbcTemplate, String roleName) {
String sql = "select id, role_name, note from t_role where role_name like concat('%', ?, '%')";
Object[] params = {roleName};
List list = jdbcTemplate.query(sql, params, (ResultSet rs, int rowNum) -> {
Role result = new Role();
result.setId(rs.getLong("id"));
result.setRoleName(rs.getString("role_name"));
result.setNote(rs.getString("note"));
return result;
});
return list;
}
}
如果需要执行多条SQL语句,可以使用 execute 方法,它将允许传递 ConnectionCallback 或者 StatementCallback 等接口进行回调。
MyBatis-Spring 项目
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。
依旧使用前面的数据源,创建项目并添加 Maven 依赖。
4.0.0
com.wyk
springmybatisdemo
1.0-SNAPSHOT
4.3.2.RELEASE
3.4.0
org.springframework
spring-core
${spring.version}
org.springframework
spring-jdbc
${spring.version}
org.springframework
spring-aop
${spring.version}
org.springframework
spring-context-support
${spring.version}
org.mybatis
mybatis
${mybatis.version}
org.mybatis
mybatis-spring
1.3.0
mysql
mysql-connector-java
5.1.25
commons-dbcp
commons-dbcp
1.4
log4j
log4j
1.2.17
src/main/java
**/*.xml
true
然后创建实体类 Role, 添加数据源配置,和前面一样,这里不再赘述。
配置 SqlSessionFactoryBean
在 MyBatis 中, SqlSessionFactory 是产生 SqlSession 的基础,配置它十分关键。在 MyBatis-Spring 项目中提供了 SqlSessionFactoryBean 去支持 SqlSessionFactory 的配置。
在 XML 配置文件中配置 SqlSessionFactoryBean。
这里引入了 MyBatis 的配置文件 sqlMapConfig.xml, 内容如下。
接下来,编写 MyBatis 的映射接口 RoleMapper.java 和 映射文件 RoleMapper.xml 。
package com.wyk.springmybatisdemo.mapper;
import com.wyk.springmybatisdemo.domain.Role;
import org.apache.ibatis.annotations.Param;
public interface RoleMapper {
public int insertRole(Role role);
public Role getRole(@Param("id") Long id);
public int updateRole(Role role);
public int deleteRole(@Param("id") Long id);
}
insert into t_role(role_name, note) values (#{roleName}, #{note})
delete from t_role where id=#{id}
update t_role
set role_name = #{roleName},
note = #{note}
where id = #{id}
到这里就基本完成了 MyBatis 框架的代码。
SqlSessionTemplate 组件
SqlSessionTemplate 并不是一个必备组件,使用并不是很广泛,但它也有一些自己的优点。首先,它是线程安全的,其次它提供了一系列增删改查的功能。
首先对它进行配置,需要引用带参数的构造方法。
然后就可以使用了。
public class SqlSessionTemplateTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
SqlSessionTemplate sqlSessionTemplate = ctx.getBean(SqlSessionTemplate.class);
Role role = new Role();
role.setRoleName("gjj");
role.setNote("sqlSessionTempalte_note");
sqlSessionTemplate.insert("com.wyk.springmybatisdemo.mapper.RoleMapper.insertRole", role);
Long id = role.getId();
sqlSessionTemplate.selectOne("com.wyk.springmybatisdemo.mapper.RoleMapper.getRole", id);
role.setNote("update_sqlSessionTemplate");
sqlSessionTemplate.update("com.wyk.springmybatisdemo.mapper.RoleMapper.updateRole", role);
sqlSessionTemplate.delete("com.wyk.springmybatisdemo.mapper.RoleMapper.deleteRole", id);
}
}
可以看到,在 sqlSessionTemplate 中需要使用字符串表明运行哪个 SQL ,IDE 无法检查其代码逻辑,所以逐渐被人们抛弃。
需要注意的一点是,当同时配置了 SqlSessionFactory 和 sqlSessionTemplate 的时候, sqlSessionTemplate 优先级更高。
配置 MapperFactoryBean
MyBatis-Spring 还提供了 MapperFactoryBean 作为中介类,通过配置它来实现 Mapper 接口。
这样,就可以通过下面的代码去获得映射器。
RoleMapper roleMapper = ctx.getBean(RoleMapper.class);
配置 MapperScannerConfigurer
在映射文件较多的时候,一个一个配置会导致配置泛滥,因此提供了类 MapperScannerConfigurer, 通过扫描的形式进行配置。
首先介绍下 MapperScannerConfigurer 的主要配置。
- basePackage: 需要扫描的包,多个用逗号隔开。
- annotationClass: 表示扫描被注解标识的类,建议开发方式,一般使用注解 @Repository 标识数据访问层。
- SqlSessionBeanName: 指定在 Spring 中定义的 SqlFactoryBean 的 Bean 名称。
- markerInterface: 指定实现了什么接口就认为它是 Mapper,需要提供一个公共接口。
可以看到有2种配置方式,一种是添加注解,一种是添加公共扩展接口,但是总是扩展一个接口会显得奇怪,一般使用注解 @Repositiory 来标注对应的 Mapper。
修改 RoleMapper。
package com.wyk.springmybatisdemo.mapper;
import com.wyk.springmybatisdemo.domain.Role;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface RoleMapper {
public int insertRole(Role role);
public Role getRole(@Param("id") Long id);
public int updateRole(Role role);
public int deleteRole(@Param("id") Long id);
}
在配置文件中添加配置。
添加测试代码,查看测试结果。
package com.wyk.springmybatisdemo.mainApp;
import com.wyk.springmybatisdemo.domain.Role;
import com.wyk.springmybatisdemo.mapper.RoleMapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringMybatisTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
RoleMapper roleMapper = ctx.getBean(RoleMapper.class);
Role role = new Role();
role.setRoleName("gjj1");
role.setNote("gjj_note");
roleMapper.insertRole(role);
Long id = role.getId();
System.out.println(id);
roleMapper.getRole(id);
role.setNote("update_note");
roleMapper.updateRole(role);
roleMapper.deleteRole(id);
}
}