以下完整示例代码地址:https://gitee.com/assad/springframework-test-daohttps://gitee.com/assad/springframework-test-dao
配置使用 Spring JDBC 的基本过程
示例模块:
site/assad/domain/User(领域对象)
site/assad/dao/UserDao(DAO对象)
site/assad/applicationContext.xml(bean配置文件)
site/assad/dao/UserDaoTest(测试)
配置 Spring JDBC
一般配置Spring JDBC 的步骤如下:
(1)定义 DataSource 数据源;
(2)定义 JdbcTemplate 模板;
(3)声明一个抽象的<bean>,用于所有 DAO 复用 JdbcTemplate 属性的配置;
(4)配置具体的DAO;
示例配置文件 applicationContext.xml 如下:
<beans ... >
<!--配置数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://127.0.0.1:3306/iogames"
p:username="root"
p:password="mysql1994assad" />
<!--配置JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource" />
<!--配置事务管理器,略-->
<!--配置事务增强,略-->
<!--扫描 DAO bean-->
<context:component-scan base-package="site.assad.dao" />
</beans>
建立 Domain 领域对象
domain 领域对象是用于对数据库中的数据表映射的对象,用于以对象操作的方式操作相应的数据表(或数据表组),即 ORM 对象映射,领域对象本身实现为一个Java Bean ;
在 Spring JDBC 这种持久化方案中,将数据表映射到领域对象,是通过在相应 DAO 对象中使用 JdbcTemplate 模板对象来进行的;
以下是示例用的领域对象,用于映射 users 表:
package site.assad.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable{
private int id;
private String name;
private String password;
private Date createDate ;
//省略 getter setter
}
建立 DAO 对象
以下为示例示例代码 UserDao 对象部分代码,用于对 User 领域对象进行持久化操作,即将 User 领域对象的操作映射为数据库中 users 相应的操作;
package site.assad.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Repository;
import site.assad.domain.User;
import java.sql.ResultSet;
import java.sql.SQLException;
//将 UserDao 标注为一个 DAO Bean
public class UserDao {
//自动注入 JdbcTemplate bean 对象
private JdbcTemplate jdbcTemplate;
//演示查询操作:通过 userName 入参查找数据库中的相应 User 对象
public User findUserByUserName(final String userName){
//sql 模板
String sqlStr = "SELECT user_id,user_name,user_password,create_date " +
"FROM users where user_name = ?";
//创建接受结果的 domain 对象
final User user = new User();
//通过 jbdcTemplate 执行 sql 查询语句,参数1:sql 模板语句,参数2:sql 模板语句中占位符的实参,参数3:对于查询结果的回调逻辑
jdbcTemplate.query(sqlStr, new Object[]{userName}, new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
user.setId(rs.getInt("user_id"));
user.setName(rs.getString("user_name"));
user.setPassword(rs.getString("user_password"));
user.setCreateDate(rs.getDate("create_date"));
}
});
return user;
}
}
通过DAO对象获取领域对象
之后在代码 Servcie 层中对于数据库相关操作,是用过特定的 DAO 对象获取相应的 domain 对象,对该domain 对象进行相应操作来进行的;
比如在 Servcie 层中调用示例代码 UserDao 的 findUserByUserName() 方法:
User user = userDao.findUserByUserName("Al-assad");
System.out.println(user);
如果要在测试文件中获取自动注入的 DAO对象,可以类似如下:
package site.assad.dao;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
locations = {"classpath:site/assad/applicationContext.xml"}) //通过注解指定配置文件路径 (
public class UserDaoTest {
//自动注入 UserDao
private UserDao userDao;
......
}
对于 Spring 配置文件的加载
(1)Java Web 项目
如果项目是一个 java web 项目,需要在项目的 web.xml 配置web监听器来启动该配置文件的全局上下文对象,并自动扫描注入bean,类似如下:
<web-app .... >
<!--加载 Spring 配置,进行 bean 的初始化-->
<!--指定配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:site/assad/applicationContext.xml</param-value>
</context-param>
<!--声明 web 容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
当然也可以如下直接通过创建 ApplicationContext 对象来手动加载相应的 DAO Bean ,但就是很不太方便,一般 Java Web 项目还是会使用以上方法来自动扫描 Bean;
(2)非 Java Web 项目
如果项目不是一个 java web 项目,可以通过 ApplicationContex 上下文来手动加载相应的 DAO Bean;(见实例代码的Test 模块):
Application ctx = new ClassPathXmlApplicationContext("site/assad/applicationContext.xml");
UserDao userDao = ctx.getBean("userDao",UserDao.class);
User user = userDao.findUserByUserName("Al-assad");
System.out.println(user);
基本的数据操作
示例模块:
site/assad/domain/User(领域对象)
site/assad/dao/UserDao(DAO对象)
site/assad/applicationContext.xml(bean配置文件)
site/assad/dao/UserDaoTest(测试)
更改数据
1)基本操作
对于更改数据操作,包括 sql 的 insert,delete,update 操作,可以使用
JdbcTemplate#update 执行这些操作,如以下DAO类的代码:
public void addUser (final User user){
//创建 sql 模板
String sqlStr = "INSERT INTO users(user_name,user_password,create_date,user_icon) " +
" VALUES(?,PASSWORD(?),?,?)";
//通过 jdbcTempalte 模板更新数据
jdbcTemplate.update(sqlStr,new Object[]{user.getName(),user.getPassword(),user.getCreateDate(),user.getIcon()});
}
实际上在使用 JdbcTemplate#update(String sql,Object[] params) 时候,JdbcTemplate 会根据 params 参数自动创建一个 PerparedStatementSetter 回调实例,这使用
和 JdbcTemplate#update(String sql,PreparedStatementSetter pss) JdbcTemplate#update(String sql,PreparedStatmentCreator psc) 效果是一样的,
一般使用时会直接使用以上示例的 update 方法,因为另外2种方法实际上没有增加额外的功能,而且会照成一定的代码冗余;
2)批量操作
JdbcTemplate 也提供了
JbdcTemplate#batchUpdate
方法用于执行批量更新操作,DAO中的示例代码如下:
public void addUsers(final List<User> userList){
final String sql = "INSERT INTO users(user_name,user_password,create_date,user_icon) " +
"VALUES (?,PASSWORD(?),?,?)";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
//指定该批次的记录数
public int getBatchSize() {
return userList.size();
}
//绑定插入的参数
public void setValues(PreparedStatement ps, int i) throws SQLException {
User user = userList.get(i);
ps.setString(1,user.getName());
ps.setString(2,user.getPassword());
ps.setDate(3, new java.sql.Date(user.getCreateDate().getTime()));
ps.setString(4,user.getIcon());
}
});
}
查询数据
JdbcTemplate 提供了
JdbcTemplate#query 用于执行查询操作,
并且提供 RowCallbackHandler ,RowMapper<T> 用于处理结果集,同时还提供 JdbcTemplate#queryForObject 和 JdbcTemplate#queryForList / JdbcTemplate#queryForMap 简便方法分别用于处理返回单值数据,多行数据;
1)处理单值查询数据
如果查询结果只是一个单值,如:“SELECT COUNT(*) FROM users” 等,此时可以使用 JdbcTempalte 提供了
JdbcTemplate#queryForObject 方法用于获取查询值;
<T> queryForObject(String sql, [Object[] args ,] Class<T> requiredType)
<T> queryForObject(String sql, [Object[] args ,] RowMapper<T> RowMapper)
以下代码块示例单值数据的查询;
//演示单值查询
//返回int值的查询
public int getUserCount(){
final String sql = "SELECT COUNT(*) FROM users";
return jdbcTemplate.queryForObject(sql,Integer.class);
}
//返回User类型的查询
public User findUserByName(final String username){
final String sqlStr = "SELECT user_id,user_name,user_password,create_date " +
"FROM users where user_name = ?";
return jdbcTemplate.queryForObject(sqlStr,new Object[]{username},User.class);
}
2)处理单行结果
可以使用 JdbcTemplate#query 方法处理单行或多行结果,在处理单行结果时,一般使用 RowCallbackHandler 处理结果集,此时能带来必 queryForObject 方法更加灵活的代码逻辑;
//演示查询操作(使用 RowCallbackHandler 处理结果集,单结果返回)
public User findUserByUserName(final String userName){
final String sqlStr = "SELECT user_id,user_name,user_password,create_date " +
"FROM users where user_name = ?"; //sql 模板
final User user = new User(); //创建接受结果的 domain 对象
//通过 jbdcTemplate 执行 sql 查询语句
jdbcTemplate.query(sqlStr, new Object[]{userName}, new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
user.setId(rs.getInt("user_id"));
user.setName(rs.getString("user_name"));
user.setPassword(rs.getString("user_password"));
user.setCreateDate(rs.getDate("create_date"));
}
});
return user;
}
3)处理多行结果
一般使用中,使用 RowMapper<T> 处理多行结果集在代码上是比较方便的,如下:
//演示查询操作(使用 RowMapper<T> 处理结果集,多结果)
public List<User> findUserByIconV2(final String icon){
final String sql = "SELECT user_id,user_name,create_date,user_icon " +
"FROM users " +
"WHERE user_icon = ?";
return jdbcTemplate.query(sql, new Object[]{icon}, new RowMapper<User>() {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("user_id"));
user.setName(rs.getString("user_name"));
user.setCreateDate(rs.getDate("create_date"));
user.setIcon(rs.getString("user_icon"));
return user;
}
});
}
当然也可以使用 RowCallbackHandler 处理多行结果集,如下:
//演示查询操作(使用 RowCallbackHandler 处理结果集,多结果返回)
public List<User> findUserByIcon(final String icon){
final String sql = "SELECT user_id,user_name,create_date,user_icon " +
"FROM users " +
"WHERE user_icon = ?";
final List<User> usersList = new ArrayList<>(); //记录结果集
jdbcTemplate.query(sql, new Object[]{icon}, new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getInt("user_id"));
user.setName(rs.getString("user_name"));
user.setCreateDate(rs.getDate("create_date"));
user.setIcon(rs.getString("user_icon"));
usersList.add(user);
}
});
return usersList;
}
※RowCallbackHandler ,RowMapper<T> 的比较
RowCallbackHandler 接口实现类是可以有状态的,无法在多个地方复用,而 RowMapper<T> 的实现类是无状态的,他的实例可以在多个地方复用;
在处理多行结果的时候,虽然 RowMapper<T> 代码冗余度更低,但是当结果集占用很大内存时,RowMapper 采用的方式是将结果集中的所有数据都放到一个 List 对象中,这样会占用大量的 JVM 内存,甚至引发 OutOfMemoryException 栈溢出异常,此时可以使用 RowCallBackHandler ,在接口方法 processRow() 中一边获取数据,一边处理完成处理,避免数据在内存中的大量堆积;
调用储存过程
JdbcTemplate 提供以下2个调用储存过程的接口方法,如下:
<T>T execute(String callString,CallableStatmentCallback<T> action)
<T>T execute(CallableStatementCreator csc,CallableStatmentCallback<T> action)
假如mysql中有以下process
delimeter //
CREATE PROCESS get_user_count(IN in_user_age INT, OUT out_num INT)
BEGIN
SELECT COUNT(*) INTO out_num FROM users WHERE user_age = in_user_age;
END
delimeter ;
在DAO类中调用该process代码如下:
public int getUserCountWithAge(int age){
String sql = "{call get_user_count(?,?)}";
Integer num = jdbcTemplate.execute(sql, new CallableStatementCallback<Integer>() {
public Integer doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
cs.setInt(1,age);
cs.registerOutParameter(2, Types.INTEGER);
cs.execute();
return cs.getInt(2);
}
});
return num;
}
处理特殊类型的数据
处理Date,DateTime型数据
对于数据库中的 Date,DateTime 型数据,在 Java 中常用的日期类型为 java.util.Date,同时 Java 提供了 java.sql.Date 类型用于支持数据库中的 Date,DateTime 类型,将 java.util.Date 转换为 java.sql.Date 过程如下:
//将java.util.Date 转换为 java.sql.Date
java.sql.Date sqlDate = new java.util.Date().getTime();
//将特定日期的 java.util.Date 转换为 java.sql.Date
String dateStr = "2017-12-14";
java.util.Date date = new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
java.sql.Date sqlDate = new java.sql.Date(date.getTime());
处理LOB(BLOB/CLOB)型数据
示例代码模块:
site/assad/domain/Game(领域对象)
site/assad/dao/GameDao(DAO对象)
site/assad/applicationContext.xml(bean配置文件)
site/assad/dao/GameDaoTest(测试)
LOB大对象类型,包括 BLOB 和 CLOB 两种类型,BLOB 一般用于储存大块的二进制数据(图片数据,视频数据等),CLOB 用于储存长文本类型;
不同数据库类型中,BLOB和CLOB的实际实现类型往往不同,如:Oracle 的 BLOB/CLOB,Mysql 的 BLOB/TEXT,SQL Server 的 IMAGE/TEXT等;
JdbcTemplate 提供了数据库无关的 LobCreateor,LobHandler 接口用于用于处理 LOB 类型数据;
在对 LOB 类型数据进行处理之前,需要配置本地JDBC抽取器和对 LobHandler 或 LobCreator 进行抽取,如下:
<!--用于操作 LOB 对象的配置-->
<!--配置本地JDBC对象抽取器-->
<bean id="nativeJdbcExtractor" class="org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor"
lazy-init="true" />
<!--使用本地jdbc对象抽取-->
<bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler"
lazy-init="true" />
其中对于不同的数据源需要使用不用的JBDC抽取器,如下:
C3P0 数据源 | org.springframework.jdbc.support.nativejdbc.C3P0NativeJdbcExtractor |
DBCP 数据源 | org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor |
JBoss 3.2.4 + 的数据源 | org.springframework.jdbc.support.nativejdbc.JBossNativeJdbcExtractor |
WebLogic 8.1+ 的数据源 | org.springframework.jdbc.support.nativejdbc.WebLogicNativeJdbcExtractor |
WebSphere 5.1+ 的数据源 | org.springframework.jdbc.support.nativejdbc.WebSphereNativeJdbcExtractor |
ObjectWeb 的 XAPool 数据源 | org.springframework.jdbc.support.nativejdbc.XaPoolNativeJdbcExtractor |
如果对本地Jdbc抽取的API没有特殊要求,使用 SimpleNativeJdbcExtractor 抽取器即可;
插入 LOB 类型数据
示例代码 Dao 中插入LOB类型数据的代码如下:
//演示对于 LOB 类型数据的插入
public void addGame(final Game game){
final String sql = "INSERT INTO games(game_name,game_description,game_thumbnail) " +
"VALUES(?,?,?)";
jdbcTemplate.execute(sql,new AbstractLobCreatingPreparedStatementCallback(this.lobHandler) {
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException, DataAccessException {
ps.setString(1,game.getName());
lobCreator.setClobAsString(ps,2,game.getDescription()); //设置 CLOB 字段
lobCreator.setBlobAsBytes(ps,3,game.getThumbnail()); //设置 BLOB 字段
}
});
}
相关的测试代码:
public void testAddGame() throws Exception{
Resource imgRes = new ClassPathResource("tmp.jpg");
byte[] mockImg = FileCopyUtils.copyToByteArray(imgRes.getFile());
Game game = new Game();
game.setName("test.io");
game.setDescription("this is test clob text");
game.setThumbnail(mockImg);
gameDao.addGame(game);
}
读取 LOB 类型数据
JdbcTemplate 默认读取 BLOB 类型的数据是以块形式读取,如下示例:
//以块数据方式读取 LOB
public Game getGameByName(final String gamename){
final String sql = "SELECT game_id,game_name,game_description,game_thumbnail " +
"FROM games " +
"WHERE game_name = ?";
Game game = new Game();
jdbcTemplate.query(sql, new Object[]{gamename}, new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
game.setId(rs.getInt("game_id"));
String description = lobHandler.getClobAsString(rs,3); //读取CLOB类型数据
byte[] thumbnail = lobHandler.getBlobAsBytes(rs,4); //读取BLOB类型数据
game.setDescription(description);
game.setThumbnail(thumbnail);
}
});
return game;
}
但是这种以块形式读取 BLOB 类型的形式,当该 BLOB 类型数据过于庞大时,会占用大量的 JVM 内存,可能会引起栈溢出,此时更适合使用流的形式读取 BLOB 数据,如下示例:
//以流数据方式读取 LOB
public void getGameThumbnailByName(final String gamename,final OutputStream out){
final String sql = "SELECT game_thumbnail " +
"FROM games " +
"WHERE game_name = ?";
Game game = new Game();
jdbcTemplate.query(sql, new Object[]{gamename},
new AbstractLobStreamingResultSetExtractor<Game>() {
protected void streamData(ResultSet rs) throws SQLException, IOException, DataAccessException {
InputStream in = lobHandler.getBlobAsBinaryStream(rs,1);
if(in != null)
FileCopyUtils.copy(in,out);
}
});
}
测试代码如下:
public void testGetThumbnailByName() throws FileNotFoundException {
FileOutputStream fileOut = new FileOutputStream("./temp.jpg");
gameDao.getGameThumbnailByName("test.io",fileOut);
}