Spring DAO(2):Spring JDBC 访问数据库




配置使用 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 如下:
 
<?xml version="1.0" encoding="UTF-8"?>
<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
@Repository
public class UserDao {
    //自动注入 JdbcTemplate bean 对象
    @Autowired
    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() {
            @Override
            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;
@ContextConfiguration(locations = {"classpath:site/assad/applicationContext.xml"})  //通过注解指定配置文件路径
public class UserDaoTest {
    @Autowired  //自动注入 UserDao
    private UserDao userDao;
    @Test
   ......
}


对于 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() {
            @Override //指定该批次的记录数
            public int getBatchSize() {
                return userList.size();
            }
            @Override //绑定插入的参数
            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() {
            @Override
            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>() {
            @Override
            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() {
            @Override
            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>() {
            @Override
            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) {
            @Override
            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 字段
            }
        });
    }
相关的测试代码:
 
 @Test
    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() {
            @Override
            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>() {
                    @Override
                    protected void streamData(ResultSet rs) throws SQLException, IOException, DataAccessException {
                        InputStream in = lobHandler.getBlobAsBinaryStream(rs,1);
                        if(in != null)
                            FileCopyUtils.copy(in,out);
                    }
                });
    }
测试代码如下:
    @Test
    public void testGetThumbnailByName() throws FileNotFoundException {
        FileOutputStream fileOut = new FileOutputStream("./temp.jpg");
        gameDao.getGameThumbnailByName("test.io",fileOut);
    }



)处理单值数据

如果查询结果只是一个单值,如:“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值数据的查询过程;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值