解开代码束缚!让JAVA更便捷地访问数据库——JdbcTemplate讲解

上一篇博客讲了java程序连接数据库的基本操作,这次进一步提高java访问数据库的安全性和便捷性:

使用预编译语句对象防止SQL注入

statment又耗资源又不安全,操作数据库应该用prepareStatement对象。因为以下原因

  • 数据库接受到sql语句后还会进行编译,才能执行,就像JVM要编译执行一样,每次收到statement语句,数据库都会编译一次,这就很耗资源了。

  • prepareStatement会先把预编译语句发给数据库,然后再传入参数,这样数据库只需要编译一次。

  • prepareStatement 对象引用着预编译后的结果,每次set都是把参数直接传给数据库,就像直接把参数传给class 文件一样。

    private static void testInsert() {
            String sql = "INSERT INTO account (name,balance,create_date,cost) values(?,?,?,?)";
            try {
                Connection connection = JDBCUtil.getConnection();
                PreparedStatement pstm = connection.prepareStatement(sql);
                try {
                    pstm.setObject(1, "黎明");
                    pstm.setObject(2, 300);
                    pstm.setObject(3, "2018-06-20");
                    pstm.setObject(4,30.0);
                    pstm.executeUpdate();
                    testSelect();
                }finally {
                    JDBCUtil.close(connection,pstm);
                }
            } catch (SQLException e) {
                System.out.println("无法创建连接");
            }
        }
    
  • preparedStatement.setObject() 的效果就是自动会帮我们选择正确的类型,不用我们自己去处理

  • preparedStatement.setXXX() 方法会自动给字符串加上单引号,不用我们加。但也因此不能把表和数值变成“?”以后再指定值,因为表和数值不能取用引号括住的形式。

  • preparedStatement.clearParameters() 可以清空设置的参数,参数除非被覆盖,否则会一直在preparedStatement中存在,这样preparedStatement被多次调用时就会一直用同一个值。

用数据池优化效率

  • 操作数据库的核心是:做完一个操作就得断开连接,不要霸占数据库资源。执行完一个select就得断开,update完就得断开这样。这样就涉及大量断开和重连操作,及其耗时,所以应该用线程池来管理连接。
  • 数据池会先创建一批连接,有人需要了就把连接给他用,用完了就放回到数据池。不会断开连接,这样就省下了重开连接和断开的资源。跟线程池的原理一样的。
  • 我们知道,创建连接需要URL,用户名和密码,所以线程池一般都是用同一个用户或少数几个用户来操作数据库的。

C3P0数据池

C3P0 数据池需要依赖两个jar包:

  • c3p0-0.9.5.2.jar
  • mchange-commons-java-0.2.11.jar

只导入一个会报错的。

一般用new CombopooledDataSource()这个构造方法来新建C3P0数据池。

  • new CombopooledDataSource()这个构造器会通过class.getResourceAsStream() 的方式从模块下c3p0-config.xml文件中找配置数据。位置和名字都是不能变的

    By default, c3p0 will look for an XML configuration file in its classloader’s resource path under the name “/c3p0-config.xml”.

  • new CombopooledDataSource()构造函数的参数并不是文件的路径或者文件名,而是XML文件中的named-config标签的name属性。

  • 这个构造方法返回一个DataSource对象,我们可以DataSource.getConnection() 获得连接,之后就按部就班了。

  • 比较关键的点是要注意c3p0-config.xml 这个文件要放在src下,这样编译才会把它放到输出文件夹。

  • 还有一个关键点就是c3p0-config.xml 文件要包括以下内容:

    <c3p0-config>
        <!-- 使用默认的配置读取连接池对象 -->
        <default-config>
            <!--  数据库连接参数 -->
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/db_temp</property>
            <property name="user">root</property>
            <property name="password">admin</property>
    
            <!-- 连接池参数 -->
            <property name="initialPoolSize">5</property>
            <property name="maxPoolSize">10</property>
            <property name="checkoutTimeout">3000</property>
        </default-config>
    
        <named-config name="otherc3p0">
            <!--  连接参数 -->
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/day19</property>
            <property name="user">root</property>
            <property name="password">root</property>
    
            <!-- 连接池参数 -->
            <property name="initialPoolSize">5</property>
            <property name="maxPoolSize">6666</property>
            <property name="checkoutTimeout">1000</property>
        </named-config>
    
        <named-config name="abc">
            <!--  连接参数 -->
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/day18</property>
            <property name="user">root</property>
            <property name="password">root</property>
    
            <!-- 连接池参数 -->
            <property name="initialPoolSize">5</property>
            <property name="maxPoolSize">6</property>
            <property name="checkoutTimeout">1000</property>
        </named-config>
    </c3p0-config>
    
  • c3p0数据池完整案例:

    public class JDBC09 {
        public static void main(String[] args) throws SQLException {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            Connection connection = ds.getConnection();
            PreparedStatement pstmt = connection.prepareStatement("select * from account");
            ResultSet resultSet = pstmt.executeQuery();
            JDBC_7.showRS(resultSet);
        }
    }
    

Durid数据池

德鲁伊数据池的性能比C3P0好,是阿里巴巴开源项目。

  • druid跟C3P0差不多,但是配置不用XML而是用Properties文件,Properties文件就可以自己喜欢放哪里就放哪里,但最好还是叫derud.properties 然后还是放在src文件下吧

  • druid 的properties文件属性名不用死记,打开com.alibaba.druid.pool.DruidDataSourceFactory 这个类就啥都有了,比较主要的是以下几个属性,跟C3P0大同小异:

    • url jdbc:mysql://localhost:3306/db_name
    • username 一般都是同一个用户组的人共用一个用户操作数据库
    • password
    • maxActive 最多能有几个链接
    • initialSize 一开始新建多少个链接
    • maxWait 毫秒值,最多等多少毫秒就报错
    • 注意啊,properties文件不能有空格的!
  • druid 是用工厂方法新建对象的:

    public class JDBC_9_Durid {
        public static void main(String[] args) throws Exception {
            Properties pp = new Properties();
            pp.load(JDBC_9_Durid.class.getResourceAsStream("/druid.properties"));
            DataSource dataSource = DruidDataSourceFactory.createDataSource(pp);
            Connection connection = dataSource.getConnection();
            PreparedStatement pstm = connection.prepareStatement("select  * from account");
            ResultSet resultSet = pstm.executeQuery();
            JDBC_7.showRS(resultSet);
        }
    }
    

用Spring的JdbcTemplate进一步优化代码

  • JdbcTemplateSpring框架 已经帮我们写好的一个模板,可以自动帮我们处理connection和statement,自动帮我们关闭连接这样。

  • 因为是第三方,也是要导入jar包,JdbcTemplate需要的包有点多:

    • spring-beans-4.1.2.RELEASE.jar
    • spring-core-4.1.2.RELEASE.jar
    • spring-jdbc-4.1.2.RELEASE.jar
    • spring-tx-4.1.2.RELEASE.jar
    • com.springsource.org.apache.commons.logging-1.1.1.jar

    五个包缺一不可。

  • 通过构造函数new JdbcTemplate(dataSource) 可以传入dataSource对象获得一个jdbcTemplate对象,

  • 因为jdbcTemplate是线程安全的,多个线程使用它也不会发生同步问题,所以可以放在一个public static域中,大家一起用。

  • jdbcTemplate的优势就是,可以让我们把精力放在sql语句本身,而不用关心关闭连接之类的细节

JdbcTemplate的两类主要方法

创建了JdbcTemplate以后,我们就主要关心两类方法:

Update方法用于执行Select以外的sql语句

Update()方法需要注意以下几个点

  • JdbcTemplate.Update(String sql,Object... args) 这个方法接受一个sql语句,以及一个可变参数作为参数,会调用PreparedStatement来执行命令,如果只是update(String sql) 则只调用statement。

    private static void testUpdate() {
            String sql = "Update account set balance =? where id=?";
            int update = template.update(sql, 500, 18);
            System.out.println("update = " + update);
        }
    
  • JdbcTemplate.batchUpdate(String sql,List<Object[]> args) 可以一次执行多条语句

如果用NamedParameterJdbcTemplate 代替JdbcTemplate 就能给每个不确定的参数起别名,而不是靠顺序地插入。

  • namedParameterJdbcTemplate.update(String sql,Map<String,Object> map) 可以把别名和值组合成一个map,然后执行:

    private static void testInsertMap() {
            String sql = "update account set balance = :newBalance where name = :name ";
            Map<String, Object> map = new HashMap<>();
            map.put("newBalance", 2000);
            map.put("name", "黎明");
            namedParameterJdbcTemplate.update(sql, map);
        }
    
  • namedParameterJdbcTemplate.update(String sql,SqlParameterSource sps) 接受一个SqlParameterSource对象,通过4个方法确定一个参数的值和类型:

    • 有一个BeanPropertiesParameterSource 类是已经写好的SqlParameterSource实现类,他可以接受一个javaBean对象,通过反射快速获得这些值,但要求是传入的javaBean必须有别名的get()`方法

      String sql = "update account set balance = :balance where name = :name ";
              Account account = new Account();
              account.setBalance(300);
              account.setName("黎明"); // 要求Account对象一定要有getBalance()和getName()方法
              BeanPropertySqlParameterSource bpsps = new BeanPropertySqlParameterSource(account);
              namedParameterJdbcTemplate.update(sql, bpsps);
      
Query各种变体用于执行select的sql语句

如果是用JdbcTemplate,则只有4中query方法可以使用:

  • T jdbcTemplate.queryForObject(String sql,Class<T> class, Object... args)
    • 通过顺序来确定sql中不确定的值的具体参数
    • 通过传入类型信息把获得的值转换成需要的类型,其实可以看成是强转。
    • 接要求返回的结果是单行单列的。
    • 方法有一个重载是接受RowMapper对象的,那个不是强转,而是通过RowMapper 把每一行变成一个对象,然后返回。那个可以是单行多列。
    • 需要RowMapper的形参,可以直接传入BeanPropertiesRowMapper对象。
  • Map<String ,Object> jdbcTemplate.queryForMap(String sql,Object... args)
    • 就把得到的表以resultSet.getMetaDate().getColumnlabel()的值为键,以resultSet.getObject() 为值组成Map
    • getColumnLabel() 这个方法是,如果有给列取别名就是返回别名,否则就返回列名。
    • 要求返回的表时单列的。
  • List<Map<String,Object>> jdbcTemplate.queryForList(String sql,Object... args)
    • 就等于把多行数据变成Map以后,把Map存在List中返回。
    • 对返回的表无限制。
  • List<T> query(String sql, RowMapper<T> rowMapper , Object... args) 得到的每行数据,都会经过rowMapper处理,得到一个由rowMapper指定的泛型对象,最后把泛型对象放到List集合中返回。
    • 可以直接传入BeanPropertiesRowMapper对象, 他接受一个Class<T> 参数,用来生成返回对象。
    • BeanPropertiesRowMapper 对象是怎样指定列明对应的属性的呢?他是用getLabel() 得到的值,去匹配参数的class对象每一个有写set的属性,相同就认为是一个属性。就会setProperties。

如果用NamedParameterJdbcTemplate 来query,跟JdbcTemplate的query类方法没什么区别,除了以下几点

  • jdbcTempalte的query类方法接受的sql是多个问号组成的参数坑,而namedParameterJdbcTemplate 接受的sql是":name" 这种冒号+字符串 形式组成的参数坑。
  • jdbcTemplate 确定参数的方法是通过参数的顺序,排第一的参数对应第一个问号,如此类推。namedParameterJdbcTemplate 确定参数的方法有两种方法
    1. Map 对象,冒号后面的别名作为键,想要传入的参数作为值。
    2. SqlParameterSource 对象,别名和值对应关系的逻辑上面已经说过了。
  • 所以我们看到namedParameterJdbcTemplate 的query方法跟JdbcTemplate的大同小异,只是把Object... 参数变成了map或者sqlparameterSource所以 ,那肯定是namedParameterJdbcTemplate比较安全和方便。
private static void testNamedQuery() {
        String sql = "select name ,balance,create_date from account where name =:name";
        Account account = new Account();
        account.setName("黎明");
        List<Account> accountList = namedParameterJdbcTemplate.query(sql,
                new BeanPropertySqlParameterSource(account),
                new BeanPropertyRowMapper<>(Account.class));
        for (Account accoutnInList : accountList) {
            System.out.println(accoutnInList);
        }
    }
  • 但是namedParameterJdbcTemplate在处理数据库中IN()等语句时,就力不从心了,两者各有所长,还是看实际运用。

最后,要不就用JdbcTemplate,要不就用NamedParameterJdbcTemplate,两者选其一,不要用同一个数据池作为参数生成两个不同类型的template。数据池是线程不安全的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值