zoukankan      html  css  js  c++  java
  • JdbcTemplate(NamedParameterJdbcTemplate)详解

    Spring对数据库的操作在jdbc上面做了深层次的封装,提供了JdbcTemplate模板。

    传统Jdbc API与Spring jdbcTemplate比较:

    //JDBC API
    Statement statement = conn.createStatement();
    ResultSet resultSet = statement.executeQuery("select count(*) from COUNT student")
    if(resultSet.next()){
        Integer count = resultSet.getInt("COUNT");
    }
    
    //JDBC Template
    Integer count = jdbcTemplate.queryForObject("select count(*) from student",Integer.class);

    一、JdbcTemplate主要几类方法

    • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
    • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
    • query方法及queryForXXX方法:用于执行查询相关语句;
    • call方法:用于执行存储过程、函数相关语句(了解即可)。

    二、JdbcTemplate常用API

    //update操作单个增删改
    int update(String sql,Object[] args)
    int update(String sql,Objcet... args)
    
    //batchUpdate批量增删改
    int[] batchUpdate(String[] sql)
    int[] batchUpdate(String sql,List<Object[]>)
    
    //单个简单查询
    T queryForObjcet(String sql,Class<T> type)
    T queryForObjcet(String sql,Object[] args,Class<T> type)
    T queryForObjcet(String sql,Class<T> type,Object... arg)
    
    //获取多个
    List<T> queryForList(String sql,Class<T> type)
    List<T> queryForList(String sql,Object[] args,Class<T> type)
    List<T> queryForList(String sql,Class<T> type,Object... arg)

     查询复杂对象(封装为Map):

    //获取单个
    Map queryForMap(String sql)
    Map queryForMap(String sql,Objcet[] args)
    Map queryForMap(String sql,Object... arg)
    
    //获取多个
    List<Map<String,Object>> queryForList(String sql)
    List<Map<String,Object>> queryForList(String sql,Obgject[] args)
    List<Map<String,Object>> queryForList(String sql,Obgject... arg)

    查询复杂对象(封装为实体对象):

    Spring JdbcTemplate是通过实现org.springframework.jdbc.core.RowMapper这个接口来完成对entity对象映射。

    //获取单个
    T queryForObject(String sql,RowMapper<T> mapper)
    T queryForObject(String sql,object[] args,RowMapper<T> mapper)
    T queryForObject(String sql,RowMapper<T> mapper,Object... arg)
    
    //获取多个
    List<T> query(String sql,RowMapper<T> mapper)
    List<T> query(String sql,Object[] args,RowMapper<T> mapper)
    List<T> query(String sql,RowMapper<T> mapper,Object... arg)

    pring JDBC中目前有两个主要的RowMapper实现,使用它们应该能解决大部分的场景了:SingleColumnRowMapper和BeanPropertyRowMapper。
    SingleColumnRowMapper:返回单列数据
    BeanPropertyRowMapper:当查询数据库返回的是多列数据,且需要将这些多列数据映射到某个具体的实体类上。

    //示例:
    String sql = "select name from test_student where id = ?";
    jdbcTemplate.queryForObject(sql, new Object[]{id}, new SingleColumnRowMapper<>(String.class));
    
    String sql = "select name, gender from test_student where name = ?";
    jdbcTemplate.queryForObject(sql, new Object[]{name},new BeanPropertyRowMapper<>(Student.class));

    定义自己的RowMapper

    如果你SQL查询出来的数据列名就是和实体类的属性名不一样,或者想按照自己的规则来装配实体类,那么就可以定义并使用自己的Row Mapper。

    //自定义
    public class StudentRowMapper implements RowMapper<Student> {
    
        @Override
        public Student mapRow(ResultSet rs, int i) throws SQLException {
            Student student = new Student();
            student.setName(rs.getString("name"));
            student.setGender(rs.getString("gender"));
            student.setEmail(rs.getString("email"));
            return student;
        }
    }
    
    //使用
    String sql = "select name, gender, email from test_student where name = ?";

    三、JdbcTemplate支持的回调类

    1. 预编译语句及存储过程创建回调:用于根据JdbcTemplate提供的连接创建相应的语句

    PreparedStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的PreparedStatement;
    CallableStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的CallableStatement;

    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.dao.DataAccessException;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.PreparedStatementCallback;
    import org.springframework.jdbc.core.PreparedStatementCreator;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class) // 关联Spring与Junit
    @ContextConfiguration(locations = { "classpath:applicationContext.xml" }) // 加载配置spring配置文件
    public class AppTest {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Test
        public void testPpreparedStatement1() {
            int count = jdbcTemplate.execute(new PreparedStatementCreator() {
                public java.sql.PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
                    return conn.prepareStatement("select count(*) from user");
                }
            }, new PreparedStatementCallback<Integer>() {
                public Integer doInPreparedStatement(java.sql.PreparedStatement pstmt)
                        throws SQLException, DataAccessException {
                    pstmt.execute();
                    ResultSet rs = pstmt.getResultSet();
                    rs.next();
                    return rs.getInt(1);
                }
            });
            System.out.println(count);
        }
    }

    首先使用PreparedStatementCreator创建一个预编译语句,其次由JdbcTemplate通过PreparedStatementCallback回调传回,由用户决定如何执行该PreparedStatement。此处我们使用的是execute方法。以下是我们mysql中常用的插入时返回自增的主键:

    public long queryBuildingInfo(Map<String, Object> map) {
        String sql = "INSERT INTO TS_DEVICE_CHANGE(DEVICE_ID,CHANGE_DEVICE_ID)VALUES(?,?) ";
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                // 设置返回的主键字段名
                PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                ps.setString(1, "1");
                ps.setString(2, "2");
                return ps;
            }
        }, keyHolder);
        // 获取到插入数据生成的ID
        return keyHolder.getKey().longValue();
    }

    2. 预编译语句设值回调:用于给预编译语句相应参数设值

    PreparedStatementSetter:通过回调获取JdbcTemplate提供的PreparedStatement,由用户来对相应的预编译语句相应参数设值;
    BatchPreparedStatementSetter:;类似于PreparedStatementSetter,但用于批处理,需要指定批处理大小;

    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    import org.junit.Assert;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.PreparedStatementSetter;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class) // 关联Spring与Junit
    @ContextConfiguration(locations = { "classpath:applicationContext.xml" }) // 加载配置spring配置文件
    public class AppTest {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Test
        public void testPreparedStatement2() {
            String insertSql = "insert into user(user_name) values (?)";
            int count = jdbcTemplate.update(insertSql, new PreparedStatementSetter() {
                public void setValues(PreparedStatement pstmt) throws SQLException {
                    pstmt.setObject(1, "mmNN");
                }
            });
            Assert.assertEquals(1, count);
            String deleteSql = "delete from user where user_name=?";
            count = jdbcTemplate.update(deleteSql, new Object[] { "mmNN" });
            Assert.assertEquals(1, count);
        }
    }

    通过JdbcTemplate的int update(String sql, PreparedStatementSetter pss)执行预编译sql,其中sql参数为“insert into user(user_name) values (?) ”,该sql有一个占位符需要在执行前设值,PreparedStatementSetter实现就是为了设值,使用setValues(PreparedStatement pstmt)回调方法设值相应的占位符位置的值。JdbcTemplate也提供一种更简单的方式“update(String sql, Object... args)”来实现设值,所以只要当使用该种方式不满足需求时才应使用PreparedStatementSetter。

    3. 自定义功能回调:提供给用户一个扩展点,用户可以在指定类型的扩展点执行任何数量需要的操作

    ConnectionCallback:通过回调获取JdbcTemplate提供的Connection,用户可在该Connection执行任何数量的操作;
    StatementCallback:通过回调获取JdbcTemplate提供的Statement,用户可以在该Statement执行任何数量的操作;
    PreparedStatementCallback:通过回调获取JdbcTemplate提供的PreparedStatement,用户可以在该PreparedStatement执行任何数量的操作;
    CallableStatementCallback:通过回调获取JdbcTemplate提供的CallableStatement,用户可以在该CallableStatement执行任何数量的操作;

    4. 结果集处理回调:通过回调处理ResultSet或将ResultSet转换为需要的形式

    RowMapper:用于将结果集每行数据转换为需要的类型,用户需实现方法mapRow(ResultSet rs, int rowNum)来完成将每行数据转换为相应的类型。
    RowCallbackHandler:用于处理ResultSet的每一行结果,用户需实现方法processRow(ResultSet rs)来完成处理,在该回调方法中无需执行rs.next(),该操作由JdbcTemplate来执行,用户只需按行获取数据然后处理即可。
    ResultSetExtractor:用于结果集数据提取,用户需实现方法extractData(ResultSet rs)来处理结果集,用户必须处理整个结果集;

    @Test
    public void testResultSet1() {
        jdbcTemplate.update("insert into user(user_name) values('name7')");
        String listSql = "select * from user where user_name=?";
        List result = jdbcTemplate.query(listSql,new Object[]{"name7"}, new RowMapper<Map>() {
            public Map mapRow(ResultSet rs, int rowNum) throws SQLException {
                Map row = new HashMap();
                row.put(rs.getInt("user_id"), rs.getString("user_name"));
                return row;
            }
        });
        Assert.assertEquals(1, result.size());//查询结果数量为1才向下执行
        jdbcTemplate.update("delete from user where user_name='name7'");
    }

    RowMapper接口提供mapRow(ResultSet rs, int rowNum)方法将结果集的每一行转换为一个Map,当然可以转换为其他类。

    @Test
    public void testResultSet2() {
        jdbcTemplate.update("insert into user(user_name) values('name5')");
        String listSql = "select * from user";
        final List result = new ArrayList();
        jdbcTemplate.query(listSql, new RowCallbackHandler() {
            public void processRow(ResultSet rs) throws SQLException {
                Map row = new HashMap();
                row.put(rs.getInt("user_id"), rs.getString("user_name"));
                result.add(row);
            }
        });
        Assert.assertEquals(1, result.size());
        jdbcTemplate.update("delete from user where user_name='name5'");
    }

    RowCallbackHandler接口也提供方法processRow(ResultSet rs),能将结果集的行转换为需要的形式。

    @Test
    public void testResultSet3() {
        jdbcTemplate.update("insert into test(name) values('name5')");
        String listSql = "select * from test";
        List result = jdbcTemplate.query(listSql, new ResultSetExtractor<List>() {
            public List extractData(ResultSet rs) throws SQLException, DataAccessException {
                List result = new ArrayList();
                while (rs.next()) {
                    Map row = new HashMap();
                    row.put(rs.getInt("id"), rs.getString("name"));
                    result.add(row);
                }
                return result;
            }
        });
        Assert.assertEquals(0, result.size());
        jdbcTemplate.update("delete from test where name='name5'");
    }

    ResultSetExtractor使用回调方法extractData(ResultSet rs)提供给用户整个结果集,让用户决定如何处理该结果集。

    当然JdbcTemplate提供更简单的queryForXXX方法,来简化开发:

    //1.查询一行数据并返回int型结果  
    jdbcTemplate.queryForInt("select count(*) from test");  
    //2. 查询一行数据并将该行数据转换为Map返回  
    jdbcTemplate.queryForMap("select * from test where name='name5'");  
    //3.查询一行任何类型的数据,最后一个参数指定返回结果类型  
    jdbcTemplate.queryForObject("select count(*) from test", Integer.class);  
    //4.查询一批数据,默认将每行数据转换为Map       
    jdbcTemplate.queryForList("select * from test");  
    //5.只查询一列数据列表,列类型是String类型,列名字是name  
    jdbcTemplate.queryForList("  
    select name from test where name=?", new Object[]{"name5"}, String.class);  
    //6.查询一批数据,返回为SqlRowSet,类似于ResultSet,但不再绑定到连接上  
    SqlRowSet rs = jdbcTemplate.queryForRowSet("select * from test");  

    四、Spring提供的JDBC模板

    • JdbcTemplate:Spring里最基本的JDBC模板,利用JDBC和简单的索引参数查询提供对数据库的简单访问。
    • NamedParameterJdbcTemplate:能够在执行查询时把值绑定到SQL里的命名参数,而不是使用索引参数。
    • SimpleJdbcTemplate:利用Java 5的特性,比如自动装箱、通用(generic)和可变参数列表来简化JDBC模板的使用。

    五、分页查询

    使用JdbcTemplate都是使用SQL进行查询的,如果需要分页,则需要对SQL进行修改。下面提供了几种常用数据库的分页SQL拼接的工具类及分页对象:

    /**
     * 分页对象
     */
    public final class PageBean<T> implements Serializable {
        //当前页码
        private Integer currentPage = 1;
        //每页显示的记录数
        private Integer pageSize = 10;
        //总记录数
        private Integer totalSize = 0;
        //本页的数据列表
        private List<T> recordList;
        //总页数
        private Integer totalPageCount = 0;
        //开始行号
        private Integer startIndex = 0;
        //页码列表的开始索引
        private Integer beginPageIndex;
        //页码列表的结束索引
        private Integer endPageIndex;
    
        public PageBean(int currentPage, int pageSize, int totalSize, List<T> recordList) {
            this.currentPage = currentPage;
            this.pageSize = pageSize;
            this.totalSize = totalSize;
            this.recordList = recordList;
            //计算beginPageIndex 和 endPageIndex
            //>>总页数不多于10页,则全部显示
            if (totalPageCount <= 10) {
                beginPageIndex = 1;
                endPageIndex = totalPageCount;
            }
            //总页数多于10页,则显示当前页附近的共10个页码
            else {
                //当前页附近的共10个页码(前4个+当前页+后5个)
                beginPageIndex = currentPage - 4;
                endPageIndex = currentPage + 5;
    
                //当前面的页码不足4个时,则显示前10个页码
                if (beginPageIndex < 1) {
                    beginPageIndex = 1;
                    endPageIndex = 10;
                }
                //当后面的页码不足5个时,则显示后10个页码
                if (endPageIndex > totalPageCount) {
                    endPageIndex = totalPageCount;
                    beginPageIndex = totalPageCount - 10 + 1;
                }
            }
        }
    
        public Integer getCurrentPage() {
            return currentPage;
        }
    
        public void setCurrentPage(Integer currentPage) {
            this.currentPage = currentPage;
        }
    
        public Integer getPageSize() {
            return pageSize;
        }
    
        public void setPageSize(Integer pageSize) {
            this.pageSize = pageSize;
        }
    
        public Integer getTotalSize() {
            return totalSize;
        }
    
        public void setTotalSize(Integer totalSize) {
            this.totalSize = totalSize;
        }
    
        public List<T> getRecordList() {
            return recordList;
        }
    
        public void setRecordList(List<T> recordList) {
            this.recordList = recordList;
        }
    
        public Integer getTotalPageCount() {
            //总页数
            totalPageCount = totalSize / pageSize;
            if (totalSize == 0 || totalSize % pageSize != 0) {
                totalPageCount++;
            }
            return totalPageCount;
        }
    
        public void setTotalPageCount(Integer totalPageCount) {
            this.totalPageCount = totalPageCount;
        }
    
        public void setStartIndex(Integer startIndex) {
            this.startIndex = startIndex;
        }
    
        public Integer getStartIndex() {
            //开始行号=(当前页数-1)x每页显示的条数
            return (currentPage - 1) * pageSize;
        }
    
        public Integer getBeginPageIndex() {
            return beginPageIndex;
        }
    
        public void setBeginPageIndex(Integer beginPageIndex) {
            this.beginPageIndex = beginPageIndex;
        }
    
        public Integer getEndPageIndex() {
            return endPageIndex;
        }
    
        public void setEndPageIndex(Integer endPageIndex) {
            this.endPageIndex = endPageIndex;
        }
    }
    View Code
    /**
     * SQL的分页工具类
     */
    public final class SqlPageUtil {
    
        private SqlPageUtil(){}
    
        /**
         * @MethodName populateMySQLPageQuerySQL
         * @Description mysql的分页拼接
         * @param sqlStr
         * @param startIndex 起始索引
         * @param currentSize 每天显示的记录数
         * @Return java.lang.String 拼接后的分页语句
         */
        public static String populateMySQLPageQuerySQL(String sqlStr, int startIndex, int currentSize){
            StringBuffer pageBuffer = new StringBuffer();
            if (startIndex == 0 && currentSize <= 0) {
                pageBuffer.append(sqlStr);
            } else {
                pageBuffer.append(sqlStr);
                pageBuffer.append(" limit " + startIndex + "," + currentSize);
            }
            return pageBuffer.toString();
        }
    
        /**
         * @MethodName populateOraclePageQuerySQL
         * @Description oracle的分页拼接
         * @param sqlStr
         * @param startIndex 起始索引
         * @param currentSize 每天显示的记录数
         * @Return java.lang.String 拼接后的分页语句
         */
        public static String populateOraclePageQuerySQL(String sqlStr, int startIndex, int currentSize) {
            StringBuffer pageBuffer = new StringBuffer();
            if (startIndex == 0 && currentSize <= 0) {
                pageBuffer.append(sqlStr);
            } else {
                pageBuffer.append(" SELECT ROW_.*, ROWNUM ROWNUM_ FROM ( ");
                pageBuffer.append(sqlStr);
                pageBuffer.append(" ) ROW_ ");
                if (startIndex == 0) {
                    pageBuffer.append(" WHERE ROWNUM <= ");
                    pageBuffer.append(startIndex + currentSize);
                } else if (currentSize <= 0) {
                    pageBuffer.append(" WHERE ROWNUM > ");
                    pageBuffer.append(startIndex);
                } else {
                    pageBuffer.insert(0, "SELECT * FROM ( ");
                    pageBuffer.append(" WHERE ROWNUM <= ");
                    pageBuffer.append(startIndex + currentSize);
                    pageBuffer.append(") WHERE ROWNUM_ > ");
                    pageBuffer.append(startIndex);
                }
            }
            return pageBuffer.toString();
        }
    
        /**
         * @MethodName populateDB2PageQuerySQL
         * @Description DB2的分页拼接
         * @param sqlStr
         * @param startIndex 起始索引
         * @param currentSize 每天显示的记录数
         * @Return java.lang.String 拼接后的分页语句
         */
        public static String populateDB2PageQuerySQL(String sqlStr, int startIndex, int currentSize) {
            if (startIndex == 0 ) {
                return currentSize>1?sqlStr + " fetch first " + currentSize + " rows only":
                        " fetch first 1 row only";
            }
            return "select * from ( select inner2_.*, rownumber() over(order by order of inner2_) as rownumber_ from ( "
                    + sqlStr + " fetch first " + String.valueOf(startIndex+currentSize)
                    + " rows only ) as inner2_ ) as inner1_ where rownumber_ > "
                    + startIndex + " order by rownumber_";
        }
    }
    View Code
  • 相关阅读:
    redis参数AOF参数的bug
    tidb损坏tikv节点怎么恢复集群
    mysql主从延时临时解决办法
    python脚本批量杀死redis链接
    pt-online-schema-change 脚本化
    mysql查看锁等信息SQL
    mongo复制集脑裂问题如何处理
    日志收集及网络包收集方案
    各浏览器下载文件名不乱码的解决办法
    java 中 byte[]、File、InputStream 互相转换
  • 原文地址:https://www.cnblogs.com/myitnews/p/12347541.html
Copyright © 2011-2022 走看看