[Bug更新]Java 反射JDBC ResultSet自动映射成对应实体

yzmm
733 阅读
[更新]:修复了查询字段映射bug,导致结果集数量=字段*字段数问题。ls.add(c);应该放到for (int i = 1; i < rsm.getColumnCount() + 1; i++) {}外面。

以前一直用Spring的JdbcTemplate查询数据,JdbcTemplate被封装的及其简单易用。某些时候不太适合使用Spring这样庞大的框架,所以自己封装了个JDBC查询的自动映射类以备后用。实现了一个简单的映射数据库表到实体类数组。

TableMapping.java:

/* 
 * Copyright yz 2016-01-14  Email:admin@javaweb.org. 
 *
 * Licensed 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 org.javaweb.core.db;

import org.javaweb.core.db.annotation.Column;
import org.javaweb.core.db.annotation.Entity;
import org.javaweb.core.utils.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据表和查询实体类映射,简化查询
 *
 * @author yz
 */
public class TableMapping {

    private static TableMapping TableMapping = null;

    /**
     * 获取 TableMapping 对象
     *
     * @return
     */
    public static TableMapping getInstance() {
        if (TableMapping == null) {
            synchronized (TableMapping.class) {
                return TableMapping = new TableMapping();
            }
        }
        return TableMapping;
    }

    /**
     * 反射获取某类的所有成员变量名和成员变量(Field[])
     *
     * @param <T>
     * @param entityClass
     * @return
     */
    protected <T> Map<String, Field> getFieldsMap(Class<T> entityClass) {
        Map<String, Field> fieldMap = new HashMap<String, Field>();
        Field[]            field    = entityClass.getDeclaredFields();

        for (int i = 0; i < field.length; i++) {
            fieldMap.put(field[i].getName().toLowerCase(), field[i]);
        }
        return fieldMap;
    }

    /**
     * 反射获取某类及其父类的所有成员变量名和成员变量(Field[])
     *
     * @param <T>
     * @param entityClass
     * @return
     */
    protected <T> Map<String, Field> getAllFieldsMap(Class<T> entityClass) {
        Map<String, Field> map = new HashMap<String, Field>();
        Class<?>           c   = entityClass;
        map.putAll(getFieldsMap(entityClass));

        while (c.getSuperclass() != null) {
            c = c.getSuperclass();
            map.putAll(getFieldsMap(c));
        }
        return map;
    }

    /**
     * 反射获取某类的所有方法名和方法Method[]
     *
     * @param <T>
     * @param entityClass
     * @return
     */
    protected <T> Map<String, Method> getMethodsMap(Class<T> entityClass) {
        Method[]            method = entityClass.getMethods();
        Map<String, Method> map    = new HashMap<String, Method>();

        for (int i = 0; i < method.length; i++) {
            map.put(method[i].getName().toLowerCase(), method[i]);
        }
        return map;
    }

    /**
     * 反射映射数据库表字段到实体层,需要实体层必须包括成员变量和对应的public set方法 如: private int id; 对应public
     * void setId(int id);方法 成员变量不考虑大小写,默认会忽略数据库字段下划线。如:user_id 等于userId
     *
     * @param <T>
     * @param connection
     * @param entityClass 需要返回的实体类类型
     * @param sql         参数 sql 查询语句
     * @param arr         可变参数,有则传,没有可忽略
     * @return
     * @throws SQLException
     */
    protected <T> List<T> tableMapping(Connection connection, String sql, Class<T> entityClass, Object... arr) throws SQLException {
        List<T> ls = new ArrayList<T>();
        try {
            ResultSet           rs        = SqlHelp.executQuery(connection, sql, arr);
            ResultSetMetaData   rsm       = rs.getMetaData();
            Map<String, Method> methodMap = getMethodsMap(entityClass);
            Map<String, Field>  fieldMap  = getAllFieldsMap(entityClass);

            while (rs.next()) {
                try {
                    T c = entityClass.newInstance();

                    //反射设值
                    for (int i = 1; i < rsm.getColumnCount() + 1; i++) {
                        String columnName    = rsm.getColumnName(i);
                        String field         = columnName.toLowerCase().replaceAll("_", "");
                        String setColumnName = "set" + field;
                        if (methodMap.containsKey(setColumnName) && fieldMap.containsKey(field)) {
                            Method method = methodMap.get(setColumnName);
                            method.invoke(c, new Object[]{rs.getObject(columnName)});
                        }
                    }
                    ls.add(c);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (SQLException e) {
            throw e;
        }
        return ls;
    }

    /**
     * 查询数据库中一条记录并映射成对应的实体类类型 如果查询结果数大于一条抛出数据访问异常
     *
     * @param <T>
     * @param connection
     * @param sql
     * @param entityClass
     * @param arr
     * @return
     * @throws SQLException
     */
    public <T> T queryForEntity(Connection connection, String sql, Class<T> entityClass, Object... arr) throws SQLException {
        List<T> ls = tableMapping(connection, sql, entityClass, arr);

        if (ls.size() > 1) {
            throw new IncorrectResultSizeDataAccessException(ls.size());
        } else if (ls.size() == 1) {
            return ls.get(0);
        } else {
            return null;
        }
    }

    /**
     * 查询数据库中任意条记录并映射成对应的实体类集合类型
     *
     * @param <T>
     * @param connection
     * @param sql
     * @param entityClass
     * @param arr
     * @return
     */
    public <T> List<T> queryForList(Connection connection, String sql, Class<T> entityClass, Object... arr) {
        try {
            return tableMapping(connection, sql, entityClass, arr);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 映射数据库表实现更新实体类即可更新对应的表,需要先查询出对应的column信息后update
     *
     * @param <T>
     * @param connection
     * @param obj
     * @return
     * @throws Exception
     */
    public <T> int update(Connection connection, Object obj) throws Exception {
        Class<T> entityClass = (Class<T>) obj.getClass();
        if (entityClass.isAnnotationPresent(Entity.class)) {
            Entity              entity     = entityClass.getAnnotation(Entity.class);
            StringBuilder       sqlBuilder = new StringBuilder("update ");
            Map<String, Method> methodMap  = getMethodsMap(entityClass);
            Map<String, Field>  fieldMap   = getAllFieldsMap(entityClass);
            Object              id         = methodMap.get("get" + entity.id()).invoke(obj, new Object[]{});
            sqlBuilder.append(entity.table()).append(" set ").append(entity.id()).append("=").append("?");
            List<Object> ls = new ArrayList<Object>();
            ls.add(id);

            for (String str : fieldMap.keySet()) {
                String field = str.toLowerCase();

                if (!entity.id().equals(field) && methodMap.containsKey("set" + field) && methodMap.containsKey("get" + field)) {
                    Method  method     = methodMap.get("set" + str);
                    String  columnName = field;//初始化列名为field的值
                    boolean updatable  = true;

                    if (method.isAnnotationPresent(Column.class)) {
                        Column column = method.getAnnotation(Column.class);
                        columnName = column.name();
                        updatable = column.updatable();//是否允许更新
                    }

                    if (updatable) {
                        Method getMethod = methodMap.get("get" + str);
                        sqlBuilder.append(", ").append(columnName).append("=").append("?");
                        ls.add(getMethod.invoke(obj, new Object[]{}));
                    }
                }
            }

            sqlBuilder.append(" where ").append(entity.id()).append("=").append("? ");
            ls.add(id);
            return SqlHelp.executUpdate(connection, sqlBuilder.toString(), ls.toArray(new Object[ls.size()]));
        }
        return 0;
    }

    /**
     * 保存数据,如果某个field不需要保存需在其set方法上添加@Column(updatable = false)
     *
     * @param <T>
     * @param connection
     * @param obj
     * @return
     * @throws Exception
     */
    public <T> int save(Connection connection, Object obj) throws Exception {
        Class<T> entityClass = (Class<T>) obj.getClass();
        if (entityClass.isAnnotationPresent(Entity.class)) {
            Entity              entity     = entityClass.getAnnotation(Entity.class);
            StringBuilder       sqlBuilder = new StringBuilder("insert into ");
            Map<String, Method> methodMap  = getMethodsMap(entityClass);
            Map<String, Field>  fieldMap   = getAllFieldsMap(entityClass);
            Object              id         = methodMap.get("get" + entity.id()).invoke(obj, new Object[]{});
            sqlBuilder.append(entity.table()).append(" (");
            List<Object> ls         = new ArrayList<Object>();
            List<String> columnList = new ArrayList<String>();

            for (String str : fieldMap.keySet()) {
                String field = str.toLowerCase();

                if (!entity.id().equals(field) && methodMap.containsKey("set" + field) && methodMap.containsKey("get" + field)) {
                    Method  method     = methodMap.get("set" + str);
                    String  columnName = field;//初始化列名为field的值
                    boolean updatable  = true;

                    if (method.isAnnotationPresent(Column.class)) {
                        Column column = method.getAnnotation(Column.class);
                        columnName = column.name();
                        updatable = column.updatable();//是否允许更新
                    }

                    if (updatable) {
                        columnList.add(columnName);
                        Method getMethod = methodMap.get("get" + str);
                        ls.add(getMethod.invoke(obj, new Object[]{}));
                    }
                }
            }
            sqlBuilder.append(StringUtils.join(columnList, ",")).append(") values (");
            for (int i = 0; i < ls.size(); i++) {
                if (i > 0) {
                    sqlBuilder.append(",");
                }
                sqlBuilder.append("?");
            }
            sqlBuilder.append(")");
            return SqlHelp.executUpdate(connection, sqlBuilder.toString(), ls.toArray(new Object[ls.size()]));
        }
        return 0;
    }

    /**
     * 通过主键获取单个实体
     *
     * @param <T>
     * @param connection
     * @param entityClass
     * @param id
     * @return
     * @throws SQLException
     */
    public <T> T getEntity(Connection connection, Class<T> entityClass, Object id) throws SQLException {
        if (entityClass.isAnnotationPresent(Entity.class)) {
            Entity entity = entityClass.getAnnotation(Entity.class);
            String sql    = "select * from " + entity.table() + " where " + entity.id() + "=" + id;
            return queryForEntity(connection, sql, entityClass);
        } else {
            return null;
        }
    }

}
/* 
 * Copyright yz 2016-01-14  Email:admin@javaweb.org. 
 *
 * Licensed 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 org.javaweb.core.db;

import com.sun.rowset.CachedRowSetImpl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;

public class SqlHelp {

    /**
     * 执行任意的SQL查询语句并返回结果集
     *
     * @param connection 一个已打开的JDBC 连接
     * @param sql        需要执行的SQL语句
     * @param prr        SQL语句中参数预编译
     * @return rs SQL查询结果集
     * @throws SQLException
     */
    public static ResultSet executQuery(Connection connection, String sql, Object... prr) throws SQLException {
        PreparedStatement pstt = null;
        ResultSet         rs   = null;

        try {
            pstt = connection.prepareStatement(sql);
            CachedRowSetImpl cachedRowSetImpl = new CachedRowSetImpl();
            for (int i = 0; i < prr.length; i++) {
                pstt.setObject(i + 1, prr[i]);
            }
            rs = pstt.executeQuery();
            cachedRowSetImpl.populate(rs);
            return cachedRowSetImpl;
        } catch (SQLException e) {
            throw e;
        } finally {
            if (pstt != null) {
                pstt.close();
            }
            if (rs != null) {
                rs.close();
            }
        }
    }

    /**
     * 执行任意的SQL更新语句并影响行数
     *
     * @param connection 一个已打开的JDBC 连接
     * @param sql        需要执行的SQL语句
     * @param prr        SQL语句中参数预编译
     * @return i SQL更新后的影响行数
     * @throws SQLException
     */
    public static int executUpdate(Connection connection, String sql, Object... prr) throws SQLException {
        PreparedStatement pstt = null;
        try {
            pstt = connection.prepareStatement(sql);
            for (int i = 0; i < prr.length; i++) {
                pstt.setObject(i + 1, prr[i]);
            }
            return pstt.executeUpdate();
        } catch (SQLException e) {
            throw e;
        } finally {
            if (pstt != null) {
                pstt.close();
            }
        }
    }

    /**
     * 批量执行任意的SQL更新语句并影响行数数组,执行后会清空传入的参数List对象
     *
     * @param connection 一个已打开的JDBC 连接
     * @param sql        需要执行的SQL语句
     * @param parameters SQL语句中参数预编译
     * @return
     * @throws SQLException
     */
    public static int[] executBatchUpdate(Connection connection, String sql, List<Object[]> parameters) throws SQLException {
        PreparedStatement pstt = null;
        try {
            pstt = connection.prepareStatement(sql);
            for (Iterator<Object[]> it = parameters.iterator(); it.hasNext(); ) {
                Object[] parameter = it.next();
                for (int i = 0; i < parameter.length; i++) {
                    pstt.setObject(i + 1, parameter[i]);
                }
                pstt.addBatch();
                it.remove();
            }
            return pstt.executeBatch();
        } catch (SQLException e) {
            throw e;
        } finally {
            if (pstt != null) {
                pstt.close();
            }
        }
    }

}

IncorrectResultSizeDataAccessException.java:

/* 
 * Copyright yz 2016-01-14  Email:admin@javaweb.org. 
 *
 * Licensed 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 org.javaweb.core.db;

import java.sql.SQLException;

/**
 * SQL查询异常,查询结果数量与期待的查询结果数不一致
 *
 * @author yz
 */
public class IncorrectResultSizeDataAccessException extends SQLException {

    private static final long serialVersionUID = -4579851381128020945L;

    public IncorrectResultSizeDataAccessException(long size) {
        super("Data access exception thrown when a result was not of the expected size,for example when expecting a single row but getting 0 or more than 1 rows.it's " + size + " rows.");
    }

}

Site.java:

package org.javaweb.core.db;

public class Site {

    private int    id;

    private int    nType;

    private int    nCodePage;

    private int    nFail;

    private int    nAlexa;

    private String siteUrl;

    private String sitePass;

    private String config;

    private String ip;

    private int    nScript;

    private String accessTime;

    private String note;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getnType() {
        return nType;
    }

    public void setnType(int nType) {
        this.nType = nType;
    }

    public int getnCodePage() {
        return nCodePage;
    }

    public void setnCodePage(int nCodePage) {
        this.nCodePage = nCodePage;
    }

    public int getnFail() {
        return nFail;
    }

    public void setnFail(int nFail) {
        this.nFail = nFail;
    }

    public int getnAlexa() {
        return nAlexa;
    }

    public void setnAlexa(int nAlexa) {
        this.nAlexa = nAlexa;
    }

    public String getSiteUrl() {
        return siteUrl;
    }

    public void setSiteUrl(String siteUrl) {
        this.siteUrl = siteUrl;
    }

    public String getSitePass() {
        return sitePass;
    }

    public void setSitePass(String sitePass) {
        this.sitePass = sitePass;
    }

    public String getConfig() {
        return config;
    }

    public void setConfig(String config) {
        this.config = config;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public int getnScript() {
        return nScript;
    }

    public void setnScript(int nScript) {
        this.nScript = nScript;
    }

    public String getAccessTime() {
        return accessTime;
    }

    public void setAccessTime(String accessTime) {
        this.accessTime = accessTime;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}


评论 (4)

tmp
在wooyun 的zone里发不了新帖.能帮忙看看jenkins 的password hash 的解密吗? <p>Jenkins专有用户的数据存放在JENKINS_HOME/users目录。users目录的结构我就不扯了,你一看就懂。users/xxx(各种用户名)</p> <p> xxx目录里面就一个文件config.xml.找到节点,如</p> <p>#jbcrypt:$2a$10$DdaWzN64JgUtLdvxWIflcuQu2fgrrMSAMabF5TSrGK5nXitqK9ZMS</p> <p>这个是加密过的. 能帮忙看看,弄一个decrypt出来吗?</p>
yzmm
http://serverfault.com/questions/299425/is-jenkins-hudson-ciphered-password-storage-safe <p>https://github.com/tweksteen/jenkins-decrypt/blob/master/decrypt.py</p>

发表评论