[Bug更新]Java 反射JDBC ResultSet自动映射成对应实体
[更新]:修复了查询字段映射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;
}
}