• Spring JdbcTemplate框架搭建及其增删改查使用指南


    Spring JdbcTemplate框架搭建及其增删改查使用指南

    前言:

    本文指在介绍spring框架中的JdbcTemplate类的使用方法,涉及基本的Spring反转控制的使用方法和JDBC的基本概念。目标是使读者能够对JdbcTemplate快速地掌握和使用。

    原创不易,转载请注明出处:Spring JdbcTemplate框架搭建及其增删改查使用指南

    http://www.zui(#¥#¥#¥#¥#¥#)daima.com/share/1724429678644224.htm

    代码下载地址:http://www.zui(#¥#¥#¥#¥#¥#)daima.com/share/1724429678644224.htm

    不明白为什么,zui(#¥#¥#¥#¥#¥#)daima会被博客园说成是违禁内容,没办法,原创的连接,我只能在zui后面加入了特殊符号。

    准备:

    1. Spring的基本概念

           Spring框架核心的思想就是建立一个Java对象的大工厂,用户只要给工厂一个指令,工厂就能将用户需要的对象根据配置文件组装好返还给用户。用户需要做的许多工作则可以写成简单的配置文件。

    2. 丑陋的JDBC代码

    Connection con = null;
    PreparedStatement pStmt = null;
    ResultSet rs = null;
    try {
         con = ods.getConnection();
         String sql = "select * from admin";
         pStmt = con.prepareStatement(sql);
         rs = pStmt.executeQuery();
         while (rs.next()) {
         }
    } catch (Exception ex) {
         try {
              con.rollback();
         } catch (SQLException sqlex) {
              sqlex.printStackTrace(System.out);
         }
         ex.printStackTrace();
    } finally {
         try {
              rs.close();
              pStmt.close();
              con.close();
         } catch (Exception e) {
              e.printStackTrace();
         }
    }
    

    以上是常见的JDBC代码,简单的select语句也需要冗长的出错处理,并且每个函数都不断地重复同样的代码。

    3. JdbcTemplate的作用

           JdbcTemplate正是为了减少上述繁琐的代码而设计出来的。它是对JDBC的一种封装,抽象我们常用的一些方法。Simple and Stupid就是它的目标。下面是完成了刚才JDBC代码同样功能的JdbcTemplate的代码:

    String sql = "select * from admin";
    
    jdbcTemplate.query(sql,new RowCallbackHandler() {
    
             public void processRow(ResultSet rs) throws SQLException {  
    
                    }
    
                } );
    

    环境搭建:

    1. 数据库的配置

          本文使用Mysql数据库,新建表admin:

    CREATE TABLE `admin` (
      `ID` bigint(20) unsigned NOT NULL auto_increment,
      `NAME` varchar(100) NOT NULL,
      `PASSWORD` varchar(200) NOT NULL,
      PRIMARY KEY  (`ID`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8
    

      

    2. Spring配置

           JdbcTemplate的使用需要有DataSource的支持,所以在配置文件中,我们首先要配置一个OracleDataSource,然后在将这个DataSource配置到JdbcTemplate里。接着将JdbcTemplate配置进DAO层,最后将DAO配置进Model层。简要的关系如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
        "http://www.springframework.org/dtd/spring-beans.dtd">
    <beans>
    	<bean id="dataSource"
    		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
    		<property name="url" value="jdbc:mysql://localhost:3306/zuidaima_admin?useUnicode=true&characterEncoding=utf-8" />
    		<property name="username" value="root" />
    		<property name="password" value="111111" />
    	</bean>
    
    	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    		<property name="dataSource">
    			<ref bean="dataSource" />
    		</property>
    	</bean>
    
    	<bean id="userDao" class="com.zuidaima.dao.impl.UserDaoImpl">
    		<property name="jdbcTemplate">
    			<ref bean="jdbcTemplate" />
    		</property>
    	</bean>
    
    	<bean id="user" class="com.zuidaima.model.User">
    		<property name="dao">
    			<ref bean="userDao" />
    		</property>
    	</bean>
    
    </beans>
    

    3. 项目截图:

    使用方法:

      1.查找

      多行查询:

    class UserRowMapper implements RowMapper {
    
            public Object mapRow(ResultSet rs,int index) throws SQLException
    
            {
    
                User u = new User();
    
                u.setId(rs.getString("ID"));
    
                u.setName(rs.getString("Name"));
    
                u.setPassword(rs.getString("Password"));
    
                return u;
    
            }
    
        }
    
    public List select(String where)
    
        {
    
            List list;      
    
            String sql = "select * from admin "+where;      
    
            list = jdbcTemplate.query(sql,new RowMapperResultReader(new UserRowMapper()));
    
            return list;
    
        }
    

    List最终返回的是满足条件的User队列。

     

    单行查询:

    public User selectById(String id){
    
        String sql = "select * from admin where id=?";
    
        final User u = new User();
    
        final Object[] params = new Object[] {id};
    
        jdbcTemplate.query(sql, params, new RowCallbackHandler(){
    
                            public void processRow(ResultSet rs) throws SQLException {
    
                                     u.setId(rs.getString("ID"));
    
                                     u.setName(rs.getString("NAME"));
    
                                     u.setPassword(rs.getString("PASSWORD"));
    
                            }                    
    
        });        
    
        return u;
    
    }
    

    2.      插入

    public void insert(User u)
    
    {
    
         String sql = "insert into admin (ID,NAME,PASSWORD) values (admin_id_seq.nextval,?,?)";
    
         Object[] params = new Object[] {
    
                                                    u.getName(),
    
                                                    u.getPassword() };
    
         jdbcTemplate.update(sql,params);
    
    }
    

     

    admin_id_seq.nextval为Oracle设置好的序列,问号“?”被params里的数据依次替代,最终执行sql。

     

    3.      修改

    非常简单:

    public void update(String how)
    
    {
    
            jdbcTemplate.update(how);
    
        }
    


    运行截图

    数据库截图

    ----------------  个人补充(抱明月)  ------------------

    jdbc中,查询应该是最复杂的。jdbctemplate也一样

    jdbctemplate中,查询,首先是先需要实现【RowCallBackHandler】或者【RowMapper】,写出一个将ResultSet转换成Bean的转换器。

    例如:上面例子中的代码:

    class UserRowMapper implements RowMapper {
    
            public Object mapRow(ResultSet rs,int index) throws SQLException
    
            {
    
                User u = new User();
    
                u.setId(rs.getString("ID"));
    
                u.setName(rs.getString("Name"));
    
                u.setPassword(rs.getString("Password"));
    
                return u;
    
            }
    
        }
    

    这样的转换器,可以单独写成一个类,也可以直接写在 Dao层类或者Bean类的内部。

    实现【RowCallBackHandler】或者【RowMapper】,是有区别的。

    简单来说,大数据量应该使用【RowCallBackHandler】,因为他是分批的查询数量。(无返回值)

    小数据量应该使用【RowMapper】,因为他是一次性将所有数据从数据库中读取出来,然后加载到内存中,如果数据量很大,那么很容易占用很大内存甚至内存溢出。(有返回值)

    还有查询分为 多行查询和单行查询。

    多行查询

    多行查询,需要用到上面提到的【RowCallBackHandler】或者【RowMapper】。

    单行查询,是具有自动转换的功能的,可以在查询的时候,直接输入Bean类的class类,可以自动转换。多行的不能自动转。

    还有QueryForXXX的方法,好像是如果没有找到数据,会出现异常

    还有当把【RowCallBackHandler】或者【RowMapper】定义成通用的类的时候,一般,都会做所有字段的转换。

    但是查询语句中往往只查询部分字段,那么【RowCallBackHandler】或者【RowMapper】中去取没有数据的字段的时候,会出现异常。

    所以最好需要判断下:

        public boolean isExistColumn(ResultSet rs, String columnName) {
            try {
                if (rs.findColumn(columnName) > 0) {
                    return true;
                }
            } catch (SQLException e) {
                return false;
            }
    
            return false;
        }
    

      

    if (isExistColumn(resultSet, "id")) {
                        product.setId(resultSet.getInt("id"));
                    }
    

      

    Spring JDBCTemplate结合DButils的自动转换的类,实现自动转换

    转载自:http://www.oschina.net/code/snippet_1029551_27218?spm=0.0.0.0.Wr5erR

    在使用String的jdbcTemplate时候,想要将返回结果ResultSet映射到一个javaBean非常麻烦,要么自己先写一个类继承ResultSetExtractor 或者 RowMapper,要么就在调用query(sql, args, new XXX<T>(){})使用内部类的方式来写,感觉都比较麻烦,后来参考了apche的DBUtils,搞了个反射,只要是符合javaBean规范的bean都可以自动将rs中的返回结果映射成对应的javaBean

    1. [代码]MyJdbcTemplate继承自Spring的JdbcTemplate,主要是增加了queryForBean方法

    package com.uncle5.pubrub.dal;
     
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.List;
     
    import org.springframework.dao.DataAccessException;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.ResultSetExtractor;
    import org.springframework.jdbc.core.RowMapper;
     
    public class MyJdbcTemplate extends JdbcTemplate {
     
        /**
         * 此处是借用DBUtils中的对应类,同时增加了对Annotation的支持
         */
        private final BasicRowProcessor convert = new BasicRowProcessor();
     
        public <T> T queryForBean(String sql, final Class<T> beanType) {
            return query(sql, new ResultSetExtractor<T>() {
     
                @Override
                public T extractData(ResultSet rs) throws SQLException,
                        DataAccessException {
                    return rs.next() ? convert.toBean(rs, beanType) : null;
                }
            });
        }
     
        public <T> T queryForBean(String sql, final Class<T> beanType,
                Object... args) {
            return query(sql, args, new ResultSetExtractor<T>() {
     
                @Override
                public T extractData(ResultSet rs) throws SQLException,
                        DataAccessException {
                    return rs.next() ? convert.toBean(rs, beanType) : null;
                }
            });
        }
     
        public <T> List<T> queryForBeanList(String sql, final Class<T> beanType) {
            return query(sql, new RowMapper<T>() {
     
                @Override
                public T mapRow(ResultSet rs, int rowNum) throws SQLException {
                    return convert.toBean(rs, beanType);
                }
            });
        }
     
        public <T> List<T> queryForBeanList(String sql, final Class<T> beanType,
                Object... args) {
            return query(sql, args, new RowMapper<T>() {
     
                @Override
                public T mapRow(ResultSet rs, int rowNum) throws SQLException {
                    return convert.toBean(rs, beanType);
                }
            });
        }
    }
    

      

    2. [代码]BasicRowProcessor,此类为DBUtils中的原声类

    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.uncle5.pubrub.dal;
     
    import java.sql.ResultSet;
    import java.sql.ResultSetMetaData;
    import java.sql.SQLException;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Locale;
    import java.util.Map;
     
    import org.apache.commons.dbutils.RowProcessor;
     
    /**
     * Basic implementation of the <code>RowProcessor</code> interface.
     * 
     * <p>
     * This class is thread-safe.
     * </p>
     * 
     * @see RowProcessor
     */
    public class BasicRowProcessor {
     
        /**
         * The default BeanProcessor instance to use if not supplied in the
         * constructor.
         */
        private static final BeanProcessor defaultConvert = new BeanProcessor();
     
        /**
         * The Singleton instance of this class.
         */
        private static final BasicRowProcessor instance = new BasicRowProcessor();
     
        /**
         * Returns the Singleton instance of this class.
         * 
         * @return The single instance of this class.
         * @deprecated Create instances with the constructors instead. This will be
         *             removed after DbUtils 1.1.
         */
        @Deprecated
        public static BasicRowProcessor instance() {
            return instance;
        }
     
        /**
         * Use this to process beans.
         */
        private final BeanProcessor convert;
     
        /**
         * BasicRowProcessor constructor. Bean processing defaults to a
         * BeanProcessor instance.
         */
        public BasicRowProcessor() {
            this(defaultConvert);
        }
     
        /**
         * BasicRowProcessor constructor.
         * 
         * @param convert
         *            The BeanProcessor to use when converting columns to bean
         *            properties.
         * @since DbUtils 1.1
         */
        public BasicRowProcessor(BeanProcessor convert) {
            super();
            this.convert = convert;
        }
     
        /**
         * Convert a <code>ResultSet</code> row into an <code>Object[]</code>. This
         * implementation copies column values into the array in the same order
         * they're returned from the <code>ResultSet</code>. Array elements will be
         * set to <code>null</code> if the column was SQL NULL.
         * 
         * @see org.apache.commons.dbutils.RowProcessor#toArray(java.sql.ResultSet)
         * @param rs
         *            ResultSet that supplies the array data
         * @throws SQLException
         *             if a database access error occurs
         * @return the newly created array
         */
        public Object[] toArray(ResultSet rs) throws SQLException {
            ResultSetMetaData meta = rs.getMetaData();
            int cols = meta.getColumnCount();
            Object[] result = new Object[cols];
     
            for (int i = 0; i < cols; i++) {
                result[i] = rs.getObject(i + 1);
            }
     
            return result;
        }
     
        /**
         * Convert a <code>ResultSet</code> row into a JavaBean. This implementation
         * delegates to a BeanProcessor instance.
         * 
         * @see org.apache.commons.dbutils.RowProcessor#toBean(java.sql.ResultSet,
         *      java.lang.Class)
         * @see org.apache.commons.dbutils.BeanProcessor#toBean(java.sql.ResultSet,
         *      java.lang.Class)
         * @param <T>
         *            The type of bean to create
         * @param rs
         *            ResultSet that supplies the bean data
         * @param type
         *            Class from which to create the bean instance
         * @throws SQLException
         *             if a database access error occurs
         * @return the newly created bean
         */
        public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
            return this.convert.toBean(rs, type);
        }
     
        /**
         * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans.
         * This implementation delegates to a BeanProcessor instance.
         * 
         * @see org.apache.commons.dbutils.RowProcessor#toBeanList(java.sql.ResultSet,
         *      java.lang.Class)
         * @see org.apache.commons.dbutils.BeanProcessor#toBeanList(java.sql.ResultSet,
         *      java.lang.Class)
         * @param <T>
         *            The type of bean to create
         * @param rs
         *            ResultSet that supplies the bean data
         * @param type
         *            Class from which to create the bean instance
         * @throws SQLException
         *             if a database access error occurs
         * @return A <code>List</code> of beans with the given type in the order
         *         they were returned by the <code>ResultSet</code>.
         */
        public <T> List<T> toBeanList(ResultSet rs, Class<T> type)
                throws SQLException {
            return this.convert.toBeanList(rs, type);
        }
     
        /**
         * Convert a <code>ResultSet</code> row into a <code>Map</code>. This
         * implementation returns a <code>Map</code> with case insensitive column
         * names as keys. Calls to <code>map.get("COL")</code> and
         * <code>map.get("col")</code> return the same value.
         * 
         * @see org.apache.commons.dbutils.RowProcessor#toMap(java.sql.ResultSet)
         * @param rs
         *            ResultSet that supplies the map data
         * @throws SQLException
         *             if a database access error occurs
         * @return the newly created Map
         */
        public Map<String, Object> toMap(ResultSet rs) throws SQLException {
            Map<String, Object> result = new CaseInsensitiveHashMap();
            ResultSetMetaData rsmd = rs.getMetaData();
            int cols = rsmd.getColumnCount();
     
            for (int i = 1; i <= cols; i++) {
                result.put(rsmd.getColumnName(i), rs.getObject(i));
            }
     
            return result;
        }
     
        /**
         * A Map that converts all keys to lowercase Strings for case insensitive
         * lookups. This is needed for the toMap() implementation because databases
         * don't consistently handle the casing of column names.
         * 
         * <p>
         * The keys are stored as they are given [BUG #DBUTILS-34], so we maintain
         * an internal mapping from lowercase keys to the real keys in order to
         * achieve the case insensitive lookup.
         * 
         * <p>
         * Note: This implementation does not allow <tt>null</tt> for key, whereas
         * {@link HashMap} does, because of the code:
         * 
         * <pre>
         * key.toString().toLowerCase()
         * </pre>
         */
        private static class CaseInsensitiveHashMap extends HashMap<String, Object> {
            /**
             * The internal mapping from lowercase keys to the real keys.
             * 
             * <p>
             * Any query operation using the key ({@link #get(Object)},
             * {@link #containsKey(Object)}) is done in three steps:
             * <ul>
             * <li>convert the parameter key to lower case</li>
             * <li>get the actual key that corresponds to the lower case key</li>
             * <li>query the map with the actual key</li>
             * </ul>
             * </p>
             */
            private final Map<String, String> lowerCaseMap = new HashMap<String, String>();
     
            /**
             * Required for serialization support.
             * 
             * @see java.io.Serializable
             */
            private static final long serialVersionUID = -2848100435296897392L;
     
            /** {@inheritDoc} */
            @Override
            public boolean containsKey(Object key) {
                Object realKey = lowerCaseMap.get(key.toString().toLowerCase(
                        Locale.ENGLISH));
                return super.containsKey(realKey);
                // Possible optimisation here:
                // Since the lowerCaseMap contains a mapping for all the keys,
                // we could just do this:
                // return lowerCaseMap.containsKey(key.toString().toLowerCase());
            }
     
            /** {@inheritDoc} */
            @Override
            public Object get(Object key) {
                Object realKey = lowerCaseMap.get(key.toString().toLowerCase(
                        Locale.ENGLISH));
                return super.get(realKey);
            }
     
            /** {@inheritDoc} */
            @Override
            public Object put(String key, Object value) {
                /*
                 * In order to keep the map and lowerCaseMap synchronized, we have
                 * to remove the old mapping before putting the new one. Indeed,
                 * oldKey and key are not necessaliry equals. (That's why we call
                 * super.remove(oldKey) and not just super.put(key, value))
                 */
                Object oldKey = lowerCaseMap.put(key.toLowerCase(Locale.ENGLISH),
                        key);
                Object oldValue = super.remove(oldKey);
                super.put(key, value);
                return oldValue;
            }
     
            /** {@inheritDoc} */
            @Override
            public void putAll(Map<? extends String, ?> m) {
                for (Map.Entry<? extends String, ?> entry : m.entrySet()) {
                    String key = entry.getKey();
                    Object value = entry.getValue();
                    this.put(key, value);
                }
            }
     
            /** {@inheritDoc} */
            @Override
            public Object remove(Object key) {
                Object realKey = lowerCaseMap.remove(key.toString().toLowerCase(
                        Locale.ENGLISH));
                return super.remove(realKey);
            }
        }
     
    }
    

      

    3. [代码]BeanProcessor,也是DBUtils中的源生类,增加了一个方法  

    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.uncle5.pubrub.dal;
     
    import java.beans.BeanInfo;
    import java.beans.IntrospectionException;
    import java.beans.Introspector;
    import java.beans.PropertyDescriptor;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.sql.ResultSet;
    import java.sql.ResultSetMetaData;
    import java.sql.SQLException;
    import java.sql.SQLXML;
    import java.sql.Timestamp;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
     
    import org.springframework.util.StringUtils;
     
    /**
     * <p>
     * <code>BeanProcessor</code> matches column names to bean property names
     * and converts <code>ResultSet</code> columns into objects for those bean
     * properties.  Subclasses should override the methods in the processing chain
     * to customize behavior.
     * </p>
     *
     * <p>
     * This class is thread-safe.
     * </p>
     *
     * @see BasicRowProcessor
     *
     * @since DbUtils 1.1
     */
    public class BeanProcessor {
     
        /**
         * Special array value used by <code>mapColumnsToProperties</code> that
         * indicates there is no bean property that matches a column from a
         * <code>ResultSet</code>.
         */
        protected static final int PROPERTY_NOT_FOUND = -1;
     
        /**
         * Set a bean's primitive properties to these defaults when SQL NULL
         * is returned.  These are the same as the defaults that ResultSet get*
         * methods return in the event of a NULL column.
         */
        private static final Map<Class<?>, Object> primitiveDefaults = new HashMap<Class<?>, Object>();
     
        /**
         * ResultSet column to bean property name overrides.
         */
        private final Map<String, String> columnToPropertyOverrides;
     
        static {
            primitiveDefaults.put(Integer.TYPE, Integer.valueOf(0));
            primitiveDefaults.put(Short.TYPE, Short.valueOf((short) 0));
            primitiveDefaults.put(Byte.TYPE, Byte.valueOf((byte) 0));
            primitiveDefaults.put(Float.TYPE, Float.valueOf(0f));
            primitiveDefaults.put(Double.TYPE, Double.valueOf(0d));
            primitiveDefaults.put(Long.TYPE, Long.valueOf(0L));
            primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
            primitiveDefaults.put(Character.TYPE, Character.valueOf((char) 0));
        }
     
        /**
         * Constructor for BeanProcessor.
         */
        public BeanProcessor() {
            this(new HashMap<String, String>());
        }
     
        /**
         * Constructor for BeanProcessor configured with column to property name overrides.
         *
         * @param columnToPropertyOverrides ResultSet column to bean property name overrides
         * @since 1.5
         */
        public BeanProcessor(Map<String, String> columnToPropertyOverrides) {
            super();
            if (columnToPropertyOverrides == null) {
                throw new IllegalArgumentException("columnToPropertyOverrides map cannot be null");
            }
            this.columnToPropertyOverrides = columnToPropertyOverrides;
        }
     
        /**
         * Convert a <code>ResultSet</code> row into a JavaBean.  This
         * implementation uses reflection and <code>BeanInfo</code> classes to
         * match column names to bean property names.  Properties are matched to
         * columns based on several factors:
         * <br/>
         * <ol>
         *     <li>
         *     The class has a writable property with the same name as a column.
         *     The name comparison is case insensitive.
         *     </li>
         *
         *     <li>
         *     The column type can be converted to the property's set method
         *     parameter type with a ResultSet.get* method.  If the conversion fails
         *     (ie. the property was an int and the column was a Timestamp) an
         *     SQLException is thrown.
         *     </li>
         * </ol>
         *
         * <p>
         * Primitive bean properties are set to their defaults when SQL NULL is
         * returned from the <code>ResultSet</code>.  Numeric fields are set to 0
         * and booleans are set to false.  Object bean properties are set to
         * <code>null</code> when SQL NULL is returned.  This is the same behavior
         * as the <code>ResultSet</code> get* methods.
         * </p>
         * @param <T> The type of bean to create
         * @param rs ResultSet that supplies the bean data
         * @param type Class from which to create the bean instance
         * @throws SQLException if a database access error occurs
         * @return the newly created bean
         */
        public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
     
            PropertyDescriptor[] props = this.propertyDescriptors(type);
     
            ResultSetMetaData rsmd = rs.getMetaData();
             
            //这里增加了对自定义annotation的支持
            createColumnToPropertyOverridesByAnnotation(type);
            int[] columnToProperty = this.mapColumnsToProperties(rsmd, props, type);
     
            return this.createBean(rs, type, props, columnToProperty);
        }
     
        /**
         * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans.
         * This implementation uses reflection and <code>BeanInfo</code> classes to
         * match column names to bean property names. Properties are matched to
         * columns based on several factors:
         * <br/>
         * <ol>
         *     <li>
         *     The class has a writable property with the same name as a column.
         *     The name comparison is case insensitive.
         *     </li>
         *
         *     <li>
         *     The column type can be converted to the property's set method
         *     parameter type with a ResultSet.get* method.  If the conversion fails
         *     (ie. the property was an int and the column was a Timestamp) an
         *     SQLException is thrown.
         *     </li>
         * </ol>
         *
         * <p>
         * Primitive bean properties are set to their defaults when SQL NULL is
         * returned from the <code>ResultSet</code>.  Numeric fields are set to 0
         * and booleans are set to false.  Object bean properties are set to
         * <code>null</code> when SQL NULL is returned.  This is the same behavior
         * as the <code>ResultSet</code> get* methods.
         * </p>
         * @param <T> The type of bean to create
         * @param rs ResultSet that supplies the bean data
         * @param type Class from which to create the bean instance
         * @throws SQLException if a database access error occurs
         * @return the newly created List of beans
         */
        public <T> List<T> toBeanList(ResultSet rs, Class<T> type) throws SQLException {
            List<T> results = new ArrayList<T>();
     
            if (!rs.next()) {
                return results;
            }
     
            PropertyDescriptor[] props = this.propertyDescriptors(type);
            ResultSetMetaData rsmd = rs.getMetaData();
            int[] columnToProperty = this.mapColumnsToProperties(rsmd, props, type);
     
            do {
                results.add(this.createBean(rs, type, props, columnToProperty));
            } while (rs.next());
     
            return results;
        }
     
        /**
         * Creates a new object and initializes its fields from the ResultSet.
         * @param <T> The type of bean to create
         * @param rs The result set.
         * @param type The bean type (the return type of the object).
         * @param props The property descriptors.
         * @param columnToProperty The column indices in the result set.
         * @return An initialized object.
         * @throws SQLException if a database error occurs.
         */
        private <T> T createBean(ResultSet rs, Class<T> type,
                PropertyDescriptor[] props, int[] columnToProperty)
                throws SQLException {
     
            T bean = this.newInstance(type);
     
            for (int i = 1; i < columnToProperty.length; i++) {
     
                if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
                    continue;
                }
     
                PropertyDescriptor prop = props[columnToProperty[i]];
                Class<?> propType = prop.getPropertyType();
     
                Object value = this.processColumn(rs, i, propType);
     
                if (propType != null && value == null && propType.isPrimitive()) {
                    value = primitiveDefaults.get(propType);
                }
     
                this.callSetter(bean, prop, value);
            }
     
            return bean;
        }
     
        /**
         * Calls the setter method on the target object for the given property.
         * If no setter method exists for the property, this method does nothing.
         * @param target The object to set the property on.
         * @param prop The property to set.
         * @param value The value to pass into the setter.
         * @throws SQLException if an error occurs setting the property.
         */
        private void callSetter(Object target, PropertyDescriptor prop, Object value)
                throws SQLException {
     
            Method setter = prop.getWriteMethod();
     
            if (setter == null) {
                return;
            }
     
            Class<?>[] params = setter.getParameterTypes();
            try {
                // convert types for some popular ones
                if (value instanceof java.util.Date) {
                    final String targetType = params[0].getName();
                    if ("java.sql.Date".equals(targetType)) {
                        value = new java.sql.Date(((java.util.Date) value).getTime());
                    } else
                    if ("java.sql.Time".equals(targetType)) {
                        value = new java.sql.Time(((java.util.Date) value).getTime());
                    } else
                    if ("java.sql.Timestamp".equals(targetType)) {
                        value = new java.sql.Timestamp(((java.util.Date) value).getTime());
                    }
                }
     
                // Don't call setter if the value object isn't the right type
                if (this.isCompatibleType(value, params[0])) {
                    setter.invoke(target, new Object[]{value});
                } else {
                  throw new SQLException(
                      "Cannot set " + prop.getName() + ": incompatible types, cannot convert "
                      + value.getClass().getName() + " to " + params[0].getName());
                      // value cannot be null here because isCompatibleType allows null
                }
     
            } catch (IllegalArgumentException e) {
                throw new SQLException(
                    "Cannot set " + prop.getName() + ": " + e.getMessage());
     
            } catch (IllegalAccessException e) {
                throw new SQLException(
                    "Cannot set " + prop.getName() + ": " + e.getMessage());
     
            } catch (InvocationTargetException e) {
                throw new SQLException(
                    "Cannot set " + prop.getName() + ": " + e.getMessage());
            }
        }
     
        /**
         * ResultSet.getObject() returns an Integer object for an INT column.  The
         * setter method for the property might take an Integer or a primitive int.
         * This method returns true if the value can be successfully passed into
         * the setter method.  Remember, Method.invoke() handles the unwrapping
         * of Integer into an int.
         *
         * @param value The value to be passed into the setter method.
         * @param type The setter's parameter type (non-null)
         * @return boolean True if the value is compatible (null => true)
         */
        private boolean isCompatibleType(Object value, Class<?> type) {
            // Do object check first, then primitives
            if (value == null || type.isInstance(value)) {
                return true;
     
            } else if (type.equals(Integer.TYPE) && Integer.class.isInstance(value)) {
                return true;
     
            } else if (type.equals(Long.TYPE) && Long.class.isInstance(value)) {
                return true;
     
            } else if (type.equals(Double.TYPE) && Double.class.isInstance(value)) {
                return true;
     
            } else if (type.equals(Float.TYPE) && Float.class.isInstance(value)) {
                return true;
     
            } else if (type.equals(Short.TYPE) && Short.class.isInstance(value)) {
                return true;
     
            } else if (type.equals(Byte.TYPE) && Byte.class.isInstance(value)) {
                return true;
     
            } else if (type.equals(Character.TYPE) && Character.class.isInstance(value)) {
                return true;
     
            } else if (type.equals(Boolean.TYPE) && Boolean.class.isInstance(value)) {
                return true;
     
            }
            return false;
     
        }
     
        /**
         * Factory method that returns a new instance of the given Class.  This
         * is called at the start of the bean creation process and may be
         * overridden to provide custom behavior like returning a cached bean
         * instance.
         * @param <T> The type of object to create
         * @param c The Class to create an object from.
         * @return A newly created object of the Class.
         * @throws SQLException if creation failed.
         */
        protected <T> T newInstance(Class<T> c) throws SQLException {
            try {
                return c.newInstance();
     
            } catch (InstantiationException e) {
                throw new SQLException(
                    "Cannot create " + c.getName() + ": " + e.getMessage());
     
            } catch (IllegalAccessException e) {
                throw new SQLException(
                    "Cannot create " + c.getName() + ": " + e.getMessage());
            }
        }
     
        /**
         * Returns a PropertyDescriptor[] for the given Class.
         *
         * @param c The Class to retrieve PropertyDescriptors for.
         * @return A PropertyDescriptor[] describing the Class.
         * @throws SQLException if introspection failed.
         */
        private PropertyDescriptor[] propertyDescriptors(Class<?> c)
            throws SQLException {
            // Introspector caches BeanInfo classes for better performance
            BeanInfo beanInfo = null;
            try {
                beanInfo = Introspector.getBeanInfo(c);
     
            } catch (IntrospectionException e) {
                throw new SQLException(
                    "Bean introspection failed: " + e.getMessage());
            }
     
            return beanInfo.getPropertyDescriptors();
        }
     
        /**
         * The positions in the returned array represent column numbers.  The
         * values stored at each position represent the index in the
         * <code>PropertyDescriptor[]</code> for the bean property that matches
         * the column name.  If no bean property was found for a column, the
         * position is set to <code>PROPERTY_NOT_FOUND</code>.
         *
         * @param rsmd The <code>ResultSetMetaData</code> containing column
         * information.
         *
         * @param props The bean property descriptors.
         *
         * @throws SQLException if a database access error occurs
         *
         * @return An int[] with column index to property index mappings.  The 0th
         * element is meaningless because JDBC column indexing starts at 1.
         */
        protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
                PropertyDescriptor[] props, Class<?> type) throws SQLException {
     
            int cols = rsmd.getColumnCount();
            int[] columnToProperty = new int[cols + 1];
            Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
     
            for (int col = 1; col <= cols; col++) {
                String columnName = rsmd.getColumnLabel(col);
                if (null == columnName || 0 == columnName.length()) {
                  columnName = rsmd.getColumnName(col);
                }
                String propertyName = columnToPropertyOverrides.get(columnName);
                if (propertyName == null) {
                    propertyName = columnName;
                }
                for (int i = 0; i < props.length; i++) {
     
                    if (propertyName.equalsIgnoreCase(props[i].getName())) {
                        columnToProperty[col] = i;
                        break;
                    }
                }
            }
     
            return columnToProperty;
        }
     
        /**
         * Convert a <code>ResultSet</code> column into an object.  Simple
         * implementations could just call <code>rs.getObject(index)</code> while
         * more complex implementations could perform type manipulation to match
         * the column's type to the bean property type.
         *
         * <p>
         * This implementation calls the appropriate <code>ResultSet</code> getter
         * method for the given property type to perform the type conversion.  If
         * the property type doesn't match one of the supported
         * <code>ResultSet</code> types, <code>getObject</code> is called.
         * </p>
         *
         * @param rs The <code>ResultSet</code> currently being processed.  It is
         * positioned on a valid row before being passed into this method.
         *
         * @param index The current column index being processed.
         *
         * @param propType The bean property type that this column needs to be
         * converted into.
         *
         * @throws SQLException if a database access error occurs
         *
         * @return The object from the <code>ResultSet</code> at the given column
         * index after optional type processing or <code>null</code> if the column
         * value was SQL NULL.
         */
        protected Object processColumn(ResultSet rs, int index, Class<?> propType)
            throws SQLException {
     
            if ( !propType.isPrimitive() && rs.getObject(index) == null ) {
                return null;
            }
     
            if (propType.equals(String.class)) {
                return rs.getString(index);
     
            } else if (
                propType.equals(Integer.TYPE) || propType.equals(Integer.class)) {
                return Integer.valueOf(rs.getInt(index));
     
            } else if (
                propType.equals(Boolean.TYPE) || propType.equals(Boolean.class)) {
                return Boolean.valueOf(rs.getBoolean(index));
     
            } else if (propType.equals(Long.TYPE) || propType.equals(Long.class)) {
                return Long.valueOf(rs.getLong(index));
     
            } else if (
                propType.equals(Double.TYPE) || propType.equals(Double.class)) {
                return Double.valueOf(rs.getDouble(index));
     
            } else if (
                propType.equals(Float.TYPE) || propType.equals(Float.class)) {
                return Float.valueOf(rs.getFloat(index));
     
            } else if (
                propType.equals(Short.TYPE) || propType.equals(Short.class)) {
                return Short.valueOf(rs.getShort(index));
     
            } else if (propType.equals(Byte.TYPE) || propType.equals(Byte.class)) {
                return Byte.valueOf(rs.getByte(index));
     
            } else if (propType.equals(Timestamp.class)) {
                return rs.getTimestamp(index);
     
            } else if (propType.equals(SQLXML.class)) {
                return rs.getSQLXML(index);
     
            } else {
                return rs.getObject(index);
            }
     
        }
     
        /**
         * 利用columnToPropertyOverrides对annotation支持
         * 
         * @param type
         */
        private <T> void createColumnToPropertyOverridesByAnnotation(Class<T> type) {
            columnToPropertyOverrides.clear();
            Field[] fields = type.getDeclaredFields();
            for(Field field : fields) {
                if(field.isAnnotationPresent(ColAlias.class)) {
                    ColAlias colAlias = field.getAnnotation(ColAlias.class);
                    if(!StringUtils.isEmpty(colAlias.value())) {
                        columnToPropertyOverrides.put(colAlias.value(), field.getName());
                    }
                }
            }
        }
    }
    

      

    4. [代码]ColAlias, 自定义的annotation,增加别名

    package com.uncle5.pubrub.dal;
     
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
     
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface ColAlias {
     
        public String value() default "";
    }
    

      

    5. [代码]javaBean实体,@ColAlias标记的是数据库字段名

    public static class ForumReply {
            private Long id;
            @ColAlias("user_id")
            private Long userId;
            @ColAlias("topic_id")
            private Long topicId;
            private String title;
            private String content;
            private int floor;
            @ColAlias("create_date")
            private Date createDate;
            private Date updateDate;
     
            public Long getId() {
                return id;
            }
     
            public void setId(Long id) {
                this.id = id;
            }
     
            public Long getUserId() {
                return userId;
            }
     
            public void setUserId(Long userId) {
                this.userId = userId;
            }
     
            public Long getTopicId() {
                return topicId;
            }
     
            public void setTopicId(Long topicId) {
                this.topicId = topicId;
            }
     
            public String getTitle() {
                return title;
            }
     
            public void setTitle(String title) {
                this.title = title;
            }
     
            public String getContent() {
                return content;
            }
     
            public void setContent(String content) {
                this.content = content;
            }
     
            public int getFloor() {
                return floor;
            }
     
            public void setFloor(int floor) {
                this.floor = floor;
            }
     
            public Date getCreateDate() {
                return createDate;
            }
     
            public void setCreateDate(Date createDate) {
                this.createDate = createDate;
            }
     
            public Date getUpdateDate() {
                return updateDate;
            }
     
            public void setUpdateDate(Date updateDate) {
                this.updateDate = updateDate;
            }
     
        }
    

      

    6. [代码]ForumReplyDaoImpl 调用例子

    package com.uncle5.pubrub.dal.forum;
     
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.List;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.RowMapper;
    import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
    import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
    import org.springframework.jdbc.core.namedparam.SqlParameterSource;
    import org.springframework.stereotype.Repository;
     
    import com.uncle5.pubrub.dal.BaseDao;
    import com.uncle5.pubrub.dal.MyJdbcTemplate;
    import com.uncle5.pubrub.dal.model.Forum.ForumReply;
     
    @Repository
    public class ForumReplyDaoImpl implements ForumReplyDao {
         
        @Autowired
        protected MyJdbcTemplate myjdbc;
     
        protected NamedParameterJdbcTemplate getNamedJdbcTemplate() {
            return new NamedParameterJdbcTemplate(myjdbc);
        }
     
        @Override
        public void saveForumReply(ForumReply forumReply) {
            int nextFloor = getNextSequence("floor");
            forumReply.setFloor(nextFloor);
     
            String sql = "insert into forum_reply(user_id, topic_id, title, content, floor, create_date) "
                    + "values(:userId, :topicId, :title, :content, :floor, now())";
            SqlParameterSource forumReplyParams = new BeanPropertySqlParameterSource(
                    forumReply);
            getNamedJdbcTemplate().update(sql, forumReplyParams);
        }
     
        @Override
        public List<ForumReply> queryForumReplysByTopicId(Long topicId) {
            String sql = "select * from forum_reply where topic_id = ?";
     
            // 原生写法
            List<ForumReply> forumReplies = myjdbc.query(sql,
                    new Object[] { topicId }, new RowMapper<ForumReply>() {
     
                        @Override
                        public ForumReply mapRow(ResultSet rs, int rowNum)
                                throws SQLException {
                            ForumReply forumReply = new ForumReply();
                            forumReply.setId(rs.getLong("id"));
                            forumReply.setTopicId(rs.getLong("topic_id"));
                            forumReply.setUserId(rs.getLong("user_id"));
                            forumReply.setTitle(rs.getString("title"));
                            forumReply.setContent(rs.getString("content"));
                            forumReply.setFloor(rs.getInt("floor"));
                            forumReply.setCreateDate(rs.getDate("create_date"));
                            forumReply.setUpdateDate(rs.getDate("update_date"));
     
                            return forumReply;
                        }
                    });
     
            // 调用queryForBeanList方法
            forumReplies = myjdbc.queryForBeanList(sql, ForumReply.class, topicId);
     
            return forumReplies;
        }
    }
    

      

    7. [代码]原理说明

    1.调用queryForBean(List)方法时候,其实只是利用原生的
     
    public <T> T query(String sql, Object[] args, ResultSetExtractor<T> rse) throws DataAccessException;
     
    方法,当得到ResultSet对象时候,调用BasicRowProcessor的
     
    public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException;
     
    方法,进而调用BeanProcessor中的,
     
    public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException;
     
    方法,(其实BasicRowProcessor这步可以省略掉,我这里没有省略,)主要可以看下
     
    protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
                PropertyDescriptor[] props, Class<?> type) throws SQLException;
     
    这个方法,其实就是将javaBean中的属性与ResultSet中columnName进行比较,如果一样,就会在接下来赋值时候
     
    调用对应rs.getXXX(int index)方法进行赋值。在这里,原生的DBUtils中的这个类已经支持了自定义别名的一个
     
    map--columnToPropertyOverrides,来对属性名称进行改写以供和ResultSet中的ColumnName进行匹配比
     
    较,我增加的也不过是根据annotation构造一个columnToPropertyOverrides,来完成这个操作。
     
    另外还有一个做法:如果sql都是自己维护的话,其实可以采用类似 
     
    select user_id as userId from table_user where ...
     
    的方式,这样就可以采用原本的jdbcTemplate操作了,写的比较乱,大家随便看看,能用就行哈。
    

    上面的利用DButils的类自动转换的功能,我也已经尝试过了,确实可以实现自动转换,但是不知道有没有什么bug。

    看了原作者的评论,但是不太明白bug指的哪里,怎么修复,说:

    昨天发现了一个bug,就是在BeanProcessor类中的toBean方法里,之前我考虑的是如果columnToPropertyOverrides为空就进行annotation支持,但是因为MyJdbcTemplate这个类被spring托管,默认为单例,也就是实际上只有一个实例化出来的BeanProcessor,这样就会出现当第一次执行createColumnToPropertyOverridesByAnnotation之后,就不会再次执行这个方法了,已经修改了,也请你这边注意下。

  • 相关阅读:
    Vue内敛模板
    vue自定义组件添加原生事件监听
    vue 组件开发 props 验证
    Vue中子组件数据跟着父组件改变和父组件数据跟着子组件改变的方法
    jQuery中outerWidth()方法
    CSS3-transition
    行内元素(例如)设置float之后才能用width调整宽度
    leetcode LRU Cache python
    opcache effect
    leetcode Same Tree python
  • 原文地址:https://www.cnblogs.com/yanqin/p/5996506.html
Copyright © 2020-2023  润新知