在前面几篇文章中,我们进行了针对于单值插入,查询(多种)的API的封装,在日常开发过程中,更新是最重要的操作之一,所以在接下来的文章中,将重点针对于更新操作进行封装。目标是封装出简便易用的API,提高开发效率。
本章节增加三个针对于对象的更新操作API:
1、单个对象的更新
2、批量插入
3、批量更新
其中单个对象的更新,我们依赖于组装update的sql来使用jdbcTemplate的update方法完成。
对于批量更新,我们使用batchUpdate来完成,我们要做的工作,主要是提取其中针对于setValue的重复操作,还是那个目标,隔离具体的数据库表和字段,我们通过使用Field[]来保存DO类的属性,然后
严格按照相同的次序生成更新sql和在setValue回调函数中进行赋值操作。这样说可能有点绕,但会看代码具体实现。
对于批量插入,我们使用SqlParameterSource来完成,当然,这里也可以像批量更新一样,使用batchUpdate来执行insert语句,但是那样做的并不见得比使用现有资源来的性价比高。同时,对于大批量的插入,我们引入了分页的机制,分批次插入以提升效率。
在测试环境下,这个效率平均在:1500-2000条/分钟。 普通台式机,双核,远程数据库条件下。在小型机等环境下或许会更好一点。
/**
* 批量插入
* <p>
* 批量将对象列表插入到数据库中,主键自动生成。
* 可以选择是否使用分页插入模式,当数据量比较大的时候可以选择,以提升性能;
* · 开启分页插入模式的条件: 1、moreThanOnce 设置 true 。 2、列表总长度大于单次最大插入数目,默认500,可修改。
*
* 使用示例
* -------------------------------------------------------
* List<Person> addlist = new ArrayList<Person>();
* Person p1 = new Person();
* p1.init(...);
* Person p2 = new Person();
* p2.init(...);
*
* addlist.add(p1); addlist.add(p2);
*
* batchInsert(addlist,Person.class);
* ...
* </p>
* @param updateList 插入列表
* @param objectClass 列表对象类型
* @throws RuntimeException
*/
@SuppressWarnings("rawtypes")
public void batchInsert(List<? extends Object> updateList,Class objectClass,boolean moreThanOnce) throws RuntimeException{
if(null == updateList || 0 == updateList.size()){
throw new RuntimeException("can not inser null value!");
}
// 获取SimpleJdbcInsert
SimpleJdbcInsert inserActor = getSimpleJdbcInsert(objectClass);
// 如果选择批量插入,并且列表长度大于单次插入最大值,则使用分页插入
if(moreThanOnce && insertPageSize < updateList.size()){
// 需要的分页总数
int totalPage = (0==updateList.size() / insertPageSize) ?
(updateList.size() / insertPageSize) : (updateList.size() / insertPageSize+1);
// 开始分页插入
for(int pageNo=1;pageNo<=totalPage;pageNo++){
int fromIndex = 0;
int endIndex = updateList.size() > insertPageSize ? insertPageSize : updateList.size();
List<? extends Object> subList = updateList.subList(fromIndex, endIndex);//[)
//insert into db
insertSqlParameterSourceInfoDB(subList,inserActor);
}
}else {
// 不分页直接插入
insertSqlParameterSourceInfoDB(updateList,inserActor);
}
}
/**
* 批量更新
* <p>
* 使用示例
* --------------------------------------------------------------------------------
* class Person{
* ①、private static final long serialVersionUID = 4311010571442992834L;//位置可以在这里
* private long id;
* ②、private static final long serialVersionUID = 4311010571442992834L;//位置可以在这里
* private String name;
* ③、private static final long serialVersionUID = 4311010571442992834L;//位置可以在这里
* }
* //上述类过滤后的顺序为 id - name
*
* updateList.add(p1);
* updateList.add(p2);
* updateList.add(p3);
*
* batchUpdate(updateList,Person.class);
* ...
* </p>
* @param updateList 更新列表
* @param objectClass 更新对象类型
* @throws RuntimeException
*/
@SuppressWarnings("rawtypes")
public void batchUpdate(final List updateList,Class objectClass) throws RuntimeException{
if(null == updateList || 0 == updateList.size()){
throw new RuntimeException("can not inser null value!");
}
// 获取一个对象
Object object = getInstanceByClass(objectClass);
// 生成更新sql,update table set .x = ?.. where id = ?
Map<String,Object> conditions = new HashMap<String,Object>();
conditions.put("id", "?");
String sql = buildUpdateObjectSql(object, false, conditions);
// 获取属性数组,其中不包括数据库中不含有的字段 serivalversionUID
final Field[] fields = getFieldsMatchedDBFromClass(objectClass,false);
/**
* 执行批量更新操作
* 生成的sql中的占位符的顺序和DO类中顺序固定,所以这里也要按照DO类中的顺序来实施
* 在Fields中包含了部分与数据库不匹配的字段,有serivalversionuid.所以应该去除掉
*/
getJdbcTemplate().batchUpdate(sql, new BatchPreparedStatementSetter(){
public int getBatchSize() {
return updateList.size();
}
// 设置参数
public void setValues(PreparedStatement preparedStatement, int index)
throws SQLException {
Object o = updateList.get(index);
for(Field field : fields){
//列从1开始计数,但是方法内部也从0开始计数,所以这里需要加1
int row =1+getFieldIndexOfObject(fields, field.getName());
String value = getValueByName(o,field.getName());
preparedStatement.setString(row, value);
}
// 设置最后的一位,即更新条件,此处为ID,sql中参数总数为fields.length,加1可以判断最后一个ID参数的位置
preparedStatement.setString(fields.length+1, getValueByName(o,DEFAULT_ID_NAME));
}
});
}
其中插入数据库的操作方法:
//执行批量插入,注意这里的返回值是没有意义的,一般由底层jdbc决定,通常会返回-2
inserActor.executeBatch(paramSources);
}
其他的代码请见下面详细:
// table name
private String tableName;
//default getter method prefix
public String GET_METHOD_PRE = "get";
//default setter method prefix
public String SET_METHOD_PRE = "set";
//default key name
public String DEFAULT_ID_NAME = "id";
//default serialVersionUID name
public String DEFAULT_SERIALVERSIONUID_NAME = "serialVersionUID";
//default max size of each insert
public int insertPageSize = 500;
//~~~ static final fields start ****************//
private final static String SQL_SELECT = " select ";
private final static String SQL_FROM = " from ";
private final static String SQL_WHERE = " where ";
private final static String SQL_AND = " and ";
private final static String SQL_UPDATE = " update ";
private final static String SQL_SET = " set ";
private final static String SQL_ORDER_BY = " order by ";
private final static String SQL_LIMIT = " limit ";
private final static String SQL_COUNT = " count(*) ";
private final static String SQL_EQUAL = " = ";
private final static String SQL_SPLIT = " , ";
private final static String SQL_PLACE_HOLDER = " ? ";
//~~~ static final fields end ****************//
/**************************************///public method begin /*******************************/
/**
* 新增一条记录
* <p>
* 要求DO:遵循spring风格的getter和setter方法
* 属性的名称和数据库的名称要对应,大小写不区分
* 示例:
* -----------------------------------------------------
* TABLE Person{
* ID BIGINT NOT NULL,
* NAME VARCHAR(16) ....
* }//数据库中字段为大写
* -----------------------------------------------------
* class Person{
* private long id;
* private String name;
* }//DO类中的字段为小写
* -----------------------------------------------------
* Person person = new Person();
* person.setName("quzishen");
* long id = insertObject(person);
*
* </p>
* @param 插入对象
* @return 主键ID
* @throws RuntimeException
*/
public int insertObject(Object object) throws RuntimeException{
if(null == object){
throw new RuntimeException("can not inser a null value!");
}
// 获取类型
@SuppressWarnings("rawtypes")
Class objectClass = object.getClass();
// 使用SimpleJdbcInsert完成插入的动作
SimpleJdbcInsert inserActor = getSimpleJdbcInsert(objectClass);
// 获取属性数组
Field[] fields = getFieldsFromClass(objectClass);
// 获取插入数据库sql需要的参数Map
Map<String, Object> paramMap = getParamMap(object, fields);
//简便起见,可以使用SqlParameterSource parameters = new BeanPropertySqlParameterSource(Actor);
// 获取到的参数为空
if(null == paramMap){
throw new RuntimeException("can not insert a null value!");
}
//执行插入并返回主键
return inserActor.executeAndReturnKey(paramMap).intValue();
}
/**
* 查询单一对象
* <p>
* 要求:遵循spring风格的getter和setter方法
* 属性的名称和数据库的名称要对应,大小写不区分
* 这里一定要注意,如果属性的大小写有点个性非常强的个人色彩,
* 那么相应的setter方法要保证'set'后的属性首字母大写,其他字母大小写保持不变
* 如:pArAm ----> getPArAm & setPArAm
* 示例:
* -------------------------------------------------------------
* class Person{
* private long id;
* }
* -------------------------------------------------------------
* Map<String,Object> params = new HashMap<String,Object>();
* params.put("id","10000");
* Person person = (Person) queryForObject(params,Person.class);
*
* </p>
* @param params 查询条件map,key 属性名字 大小写无所谓
* @param returnClass 对象类型
* @return 查询结果
*/
public Object queryForObject(Map<String,Object> params,@SuppressWarnings("rawtypes") final Class returnClass) throws RuntimeException{
//获取表名
tableName = getTableConfigName(returnClass);
// 组装查询sql,参数使用占位符
String sql = buildQuerySql(null,params,-1,-1,false);
// 查询对象,存储查询条件数组
Object[] queryObject = getQueryObject(params);
//调用queryForObject并且重新RowMapper回调方法
return getJdbcTemplate().queryForObject(sql, queryObject,new RowMapper() {
public Object mapRow(ResultSet resultSet, int rowNum) throws SQLException {
if(1 < rowNum){
throw new RuntimeException("query result is more than one!");
}
//~~~ return value 组装返回对象
Object object = getObjectFromResultSet(resultSet,returnClass);
return object;
}
});
}
/**
* 查询对象列表结果
* <p>
* 要求:遵循spring风格的getter和setter方法
* 属性的名称和数据库的名称要对应,大小写不区分
* 这里一定要注意,如果属性的大小写有点个性非常强的个人色彩,
* 那么相应的setter方法要保证'set'后的属性首字母大写,其他字母大小写保持不变
* 如:pArAm ----> getPArAm & setPArAm
* 示例:
* -------------------------------------------------------------
* class Person{
* private String desc;
* }
* -------------------------------------------------------------
* Map<String,Object> params = new HashMap<String,Object>();
* params.put("desc","10000");
* List<Person> persons = (List<Person>) queryForList(params,Person.class);
*
* </p>
* @param params 参数Map
* @param returnClass 返回列表中的对象类型
* @return
*/
@SuppressWarnings("rawtypes")
public List queryForObjectList(Map<String,Object> params,final Class returnClass){
return queryForObjectListOrderRow(params,returnClass);
}
/**
* 查询对象列表结果,查询按照指定列排序
* <p>
* 关于排序列(可变长度参数部分):
* 根据排序的列的次序依次排列,每个Map中存放一个键值对,允许指定的排序为 asc || desc
*
* 示例:
* Map<String,String> orderByName = new HashMap<String,String>();
* orderByName.put("name","asc");
*
* Map<String,String> orderByAge = new HashMap<String,String>();
* orderByName.put("age","desc");
*
* Map<String,String> orderByType = new HashMap<String,String>();
* orderByName.put("type","asc");
*
* queryForListOrderRow(params,returnClass,orderByName,orderByAge,orderByType)
* </p>
* @param params
* @param returnClass
* @param orders
* @return
*/
@SuppressWarnings("rawtypes")
public List queryForObjectListOrderRow(Map<String,Object> params,
final Class returnClass,
Map<String,String>... orders){
// 组装查询sql,参数使用占位符替代
String sql = buildQuerySql(null,params,-1,-1,false,orders);
if(logger.isDebugEnabled()){
logger.debug("RUN SQL:"+sql);
}
//执行查询
return queryForList(sql,params,returnClass);
}
/**
* 查询部分字段
* <p>
* 根据指定的属性名称查询特定字段,返回一个List<Map<属性名称,属性值>>
*
* 使用示例:
* ---------------------------------------------------------------
* //设置查询参数
* Map<String,Object> params = new HashMap();
* params.put("city","hangzhou");
*
* //设置需要查询的字段名称,名称使用DO中的即可
* List<String> fields = new ArrayList<String>();
* fields.add("id");
* fields.add(name);
* fields.add("address");
*
* List<Map<String,Object>> resultList = queryFieldsList(Person.class,params,fields);
* ...
*
* </p>
* @param objectClass 返回属性所在的类
* @param objectClass
* @param params 查询参数map
* @param returnFields 返回字段列表
* @return 返回值,用map保存每一列的结果
*/
public List<Map<String,Object>> queryFieldsList(@SuppressWarnings("rawtypes")final Class objectClass,Map<String,Object> params,List<String> returnFields){
return queryFieldsListOrderRow(objectClass,params,returnFields);
}
/**
* 查询部分字段,查询结果根据指定列排序
* <p>
* 根据指定的属性名称查询特定字段,并且根据指定的列排序,返回一个List<Map<属性名称,属性值>>
* 使用示例:
* ---------------------------------------------------------------
* //设置查询参数
* Map<String,Object> params = new HashMap();
* params.put("city","hangzhou");
*
* //设置需要查询的字段名称,名称使用DO中的即可
* List<String> fields = new ArrayList<String>();
* fields.add("id");
* fields.add(name);
* fields.add("address");
*
* Map<String,String> orderByAge = new HashMap<String,String>();
* orderByName.put("age","desc");
*
* Map<String,String> orderByType = new HashMap<String,String>();
* orderByName.put("type","asc");
*
* List<Map<String,Object>> resultList = queryFieldsList(Person.class,params,fields,orderByAge,orderByType);
* ...
*
* </p>
* @param objectClass 返回属性所在的类
* @param params 查询参数map
* @param returnFields 返回字段列表
* @param orders 查询语句排序指定字段
* @return 返回值,用map保存每一列的结果
*/
public List<Map<String,Object>> queryFieldsListOrderRow(@SuppressWarnings("rawtypes")final Class objectClass,
Map<String,Object> params,List<String> returnFields,Map<String,String>... orders){
// 组装查询sql,参数使用占位符替代
String sql = buildQuerySql(returnFields,params,-1,-1,false,orders);
//获取表名
tableName = getTableConfigName(objectClass);
return queryForMapList(sql,params,objectClass);
}
/**
* 分页查询对象
* <p>
* 根据指定参数分页查询对象列表
* 用法示例:
* ----------------------------------------------------------
* //设置查询参数
* Map<String,Object> params = new HashMap();
* params.put("city","hangzhou");
*
* Paginal p = queryObjectListForPaging(Person.class,params,1,10);
* ...
* </p>
* @param objectClass 对象类型
* @param params 查询参数
* @param pageNomber 页号
* @param pageSize 分页大小
* @return 分页封装
*/
@SuppressWarnings({ "rawtypes"})
public Paginal queryObjectListForPaging(final Class objectClass,Map<String,Object> params,int pageNomber,int pageSize){
return queryObjectListForPagingOrderRow(objectClass,params,pageNomber,pageSize);
}
/**
* 分页-排序查询对象
* <p>
* 分页查询,结果按照指定的条件排序
* 使用示例
* ----------------------------------------------------------
* //设置查询参数
* Map<String,Object> params = new HashMap();
* params.put("city","hangzhou");
*
* Map<String,String> orderByAge = new HashMap<String,String>();
* orderByName.put("age","desc");
*
* Map<String,String> orderByType = new HashMap<String,String>();
* orderByName.put("type","asc");
*
* Paginal p = queryObjectListForPagingOrderRow(Person.class,params,1,10,orderByAge,orderByType);
* ...
* </p>
* @param objectClass 字段所在类
* @param params 查询参数
* @param pageNomber 页号
* @param pageSize 分页大小
* @param orders 排序字段
* @return 查询结果
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Paginal queryObjectListForPagingOrderRow(final Class objectClass,Map<String,Object> params,int pageNomber,int pageSize,Map<String,String>... orders){
//~~~ return value
Paginal<? extends Object> paginal = new Paginal<Object>();
// 初始化分页器
paginal.setPageNomber(pageNomber);
paginal.setPageSize(pageSize);
//获取表名
tableName = getTableConfigName(objectClass);
//组装查询sql语句
String sql = buildQuerySql(null, params, paginal.getOffset(),paginal.getPageSize(),false,orders);
//查询总记录数
int totalCount = queryCount(params,objectClass);
paginal.setTotalCount(totalCount);//总记录数
paginal.setPageNum(totalCount,pageSize);//总页码
//执行查询
List resultList = queryForList(sql,params,objectClass);
paginal.setResultList(resultList);
return paginal;
}
/**
* 分页查询部分字段
* <p>
* 使用示例
* -----------------------------------------------------------------
* //设置查询参数
* Map<String,Object> params = new HashMap();
* params.put("city","hangzhou");
*
* //设置需要查询的字段名称,名称使用DO中的即可
* List<String> fields = new ArrayList<String>();
* fields.add("id");
* fields.add(name);
* fields.add("address");
*
* Paginal p = queryFieldsListForPaging(objectClass,params,fields,1,10);
* ...
* </p>
* @param objectClass 字段所在的类
* @param params 查询参数
* @param returnFields 需要查询的字段列表
* @param pageNomber 页号
* @param pageSize 分页大小
* @return 查询结果
*/
@SuppressWarnings({ "rawtypes"})
public Paginal queryFieldsListForPaging(final Class objectClass,Map<String,Object> params,List<String> returnFields,int pageNomber,int pageSize){
return queryFieldsListForPagingOrderRow(objectClass,params,returnFields,pageNomber,pageSize);
}
/**
* 分页-排序查询部分字段
* <p>
* 使用示例
* -----------------------------------------------------------------
* //设置查询参数
* Map<String,Object> params = new HashMap();
* params.put("city","hangzhou");
*
* //设置需要查询的字段名称,名称使用DO中的即可
* List<String> fields = new ArrayList<String>();
* fields.add("id");
* fields.add(name);
* fields.add("address");
*
* Map<String,String> orderByAge = new HashMap<String,String>();
* orderByName.put("age","desc");
*
* Map<String,String> orderByType = new HashMap<String,String>();
* orderByName.put("type","asc");
*
* Paginal p = queryFieldsListForPaging(objectClass,params,fields,1,10,orderByAge,orderByType);
* ...
* </p>
* @param objectClass 字段所在的类
* @param params 查询参数
* @param returnFields 需要查询的字段列表
* @param pageNomber 页号
* @param pageSize 分页大小
* @param orders 排序字段
* @return 查询结果
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Paginal queryFieldsListForPagingOrderRow(final Class objectClass,Map<String,Object> params,List<String> returnFields,int pageNomber,int pageSize,Map<String,String>... orders){
//~~~ return value
Paginal<? extends Object> paginal = new Paginal<Object>();
// 初始化分页器
paginal.setPageNomber(pageNomber);
paginal.setPageSize(pageSize);
//获取表名
tableName = getTableConfigName(objectClass);
//组装查询sql语句
String sql = buildQuerySql(returnFields, params, paginal.getOffset(),paginal.getPageSize(),false,orders);
//查询总记录数
int totalCount = queryCount(params,objectClass);
paginal.setTotalCount(totalCount);//总记录数
paginal.setPageNum(totalCount,pageSize);//总页码
//查询 得到 List<Map<String,Object>>
List resultList = queryForMapList(sql,params,objectClass);
paginal.setResultList(resultList);
return paginal;
}
/**
* 查询总记录数
* <p>
* 根据指定查询条件查询记录总数,一般用于分页中等
* </p>
* @param params 查询条件参数
* @param objectClass 对象类型
* @return 记录数
*/
@SuppressWarnings({ "rawtypes"})
public int queryCount(Map<String,Object> params,Class objectClass){
//获取表名
tableName = getTableConfigName(objectClass);
//组装查询总记录数sql语句
String countsql = buildQueryConteSql(params);
//查询对象,存储查询条件数组
Object[] queryObject = getQueryObject(params);
//查询总记录数
int totalCount = getJdbcTemplate().queryForInt(countsql, queryObject);
return totalCount;
}
/**
* 修改一条记录
* 除了ID之外的全字段修改,适用于从数据库取出的对象的修改。不适合单字段或部分字段修改。
* <p>
* 要求DO:遵循spring风格的getter和setter方法
* 属性的名称和数据库的名称要对应,大小写不区分
* 示例:
* -----------------------------------------------------
* TABLE Person{
* ID BIGINT NOT NULL,
* NAME VARCHAR(16) ....
* }//数据库中字段为大写
* -----------------------------------------------------
* class Person{
* private long id;
* private String name;
* }//DO类中的字段为小写
* -----------------------------------------------------
* Person person = new Person();
* person.setName("quzishen");
*
* Map<String,Object> conditions = new HashMap<String,Object>();
* conditions.put("ID","1");
*
* long id = updateObject(person);
*
* </p>
* @param 修改对象
* @param 查询条件,可以单独只有一个ID
* @return 主键ID
* @throws RuntimeException
*/
public int updateObject(Object object,Map<String,Object> conditions) throws RuntimeException{
if(null == object){
throw new RuntimeException("can not insert a null value!");
}
//获取表名
tableName = getTableConfigName(object.getClass());
//获取sql
String sql = buildUpdateObjectSql(object,true,conditions);
//执行更新
return getJdbcTemplate().update(sql);
}
/**
* 批量插入
* <p>
* 批量将对象列表插入到数据库中,主键自动生成。
* 可以选择是否使用分页插入模式,当数据量比较大的时候可以选择,以提升性能;
* · 开启分页插入模式的条件: 1、moreThanOnce 设置 true 。 2、列表总长度大于单次最大插入数目,默认500,可修改。
*
* 使用示例
* -------------------------------------------------------
* List<Person> addlist = new ArrayList<Person>();
* Person p1 = new Person();
* p1.init(...);
* Person p2 = new Person();
* p2.init(...);
*
* addlist.add(p1); addlist.add(p2);
*
* batchInsert(addlist,Person.class);
* ...
* </p>
* @param updateList 插入列表
* @param objectClass 列表对象类型
* @throws RuntimeException
*/
@SuppressWarnings("rawtypes")
public void batchInsert(List<? extends Object> updateList,Class objectClass,boolean moreThanOnce) throws RuntimeException{
if(null == updateList || 0 == updateList.size()){
throw new RuntimeException("can not inser null value!");
}
// 获取SimpleJdbcInsert
SimpleJdbcInsert inserActor = getSimpleJdbcInsert(objectClass);
// 如果选择批量插入,并且列表长度大于单次插入最大值,则使用分页插入
if(moreThanOnce && insertPageSize < updateList.size()){
// 需要的分页总数
int totalPage = (0==updateList.size() / insertPageSize) ?
(updateList.size() / insertPageSize) : (updateList.size() / insertPageSize+1);
// 开始分页插入
for(int pageNo=1;pageNo<=totalPage;pageNo++){
int fromIndex = 0;
int endIndex = updateList.size() > insertPageSize ? insertPageSize : updateList.size();
List<? extends Object> subList = updateList.subList(fromIndex, endIndex);//[)
//insert into db
insertSqlParameterSourceInfoDB(subList,inserActor);
}
}else {
// 不分页直接插入
insertSqlParameterSourceInfoDB(updateList,inserActor);
}
}
/**
* 批量更新
* <p>
* 使用示例
* --------------------------------------------------------------------------------
* class Person{
* ①、private static final long serialVersionUID = 4311010571442992834L;//位置可以在这里
* private long id;
* ②、private static final long serialVersionUID = 4311010571442992834L;//位置可以在这里
* private String name;
* ③、private static final long serialVersionUID = 4311010571442992834L;//位置可以在这里
* }
* //上述类过滤后的顺序为 id - name
*
* updateList.add(p1);
* updateList.add(p2);
* updateList.add(p3);
*
* batchUpdate(updateList,Person.class);
* ...
* </p>
* @param updateList 更新列表
* @param conditions 查询条件
* @param objectClass 更新对象类型
* @throws RuntimeException
*/
@SuppressWarnings("rawtypes")
public void batchUpdate(final List updateList,Map<String,Object> conditions,Class objectClass) throws RuntimeException{
if(null == updateList || 0 == updateList.size()){
throw new RuntimeException("can not inser null value!");
}
// 获取一个对象
Object object = getInstanceByClass(objectClass);
// 生成更新sql
String sql = buildUpdateObjectSql(object, false, conditions);
// 获取属性数组,其中不包括数据库中不含有的字段 serivalversionUID
final Field[] fields = getFieldsMatchedDBFromClass(objectClass,false);
/**
* 执行批量更新操作
* 生成的sql中的占位符的顺序和DO类中顺序固定,所以这里也要按照DO类中的顺序来实施
* 在Fields中包含了部分与数据库不匹配的字段,有serivalversionuid.所以应该去除掉
*/
getJdbcTemplate().batchUpdate(sql, new BatchPreparedStatementSetter(){
public int getBatchSize() {
return updateList.size();
}
// 设置参数
public void setValues(PreparedStatement preparedStatement, int index)
throws SQLException {
Object o = updateList.get(index);
for(Field field : fields){
//列从1开始计数,但是方法内部也从0开始计数,所以这里需要加1
int row =1+getFieldIndexOfObject(fields, field.getName());
String value = getValueByName(o,field.getName());
preparedStatement.setString(row, value);
}
}
});
}
/*****************************///private methods begin .../*****************************/
/**
* 获取SimpleJdbcInsert
* @param objectClass 插入对象的类型
* @return 初始化后的SimpleJdbcInsert实例
*/
private SimpleJdbcInsert getSimpleJdbcInsert(@SuppressWarnings("rawtypes") Class objectClass){
//获取表名
tableName = getTableConfigName(objectClass);
// 使用SimpleJdbcInsert完成插入的动作
SimpleJdbcInsert inserActor = new SimpleJdbcInsert(getJdbcTemplate()).withTableName(tableName);
// 获取该对象的所有的private protected publick的属性
Field[] fields = getFieldsFromClass(objectClass);
// 将属性名字保存下来
List<String> fieldNameSet = getFiledNameList(fields);
// 设置需要更新的字段以及主键
setUsingListFromNameSet(inserActor,fieldNameSet);
return inserActor;
}
/**
* 将参数列表插入到数据库中
* @param updateList 插入列表
* @param inserActor 指定的插入执行器
*/
private void insertSqlParameterSourceInfoDB(List<? extends Object> updateList,SimpleJdbcInsert inserActor){
// 使用 SqlParameterSource 封装插入参数
SqlParameterSource[] paramSources = new SqlParameterSource[updateList.size()];
int index = 0;
for(Object o : updateList){
//依次遍历列表,获取插入对象,封装成SqlParameterSource并放入数组
SqlParameterSource paramSource = new BeanPropertySqlParameterSource(o);
paramSources[index++] = paramSource;
}
//执行批量插入,注意这里的返回值是没有意义的,一般由底层jdbc决定,通常会返回-2
inserActor.executeBatch(paramSources);
}
/**
* 查询列表
* @param sql 查询sql,其中参数部分使用占位符?代替具体的值
* @param params 查询条件值的map
* @param returnClass 返回值类型
* @return 查询列表
*/
@SuppressWarnings("rawtypes")
private List queryForList(String sql,Map<String,Object> params,final Class returnClass){
//获取表名
tableName = getTableConfigName(returnClass);
// 查询对象,存储查询条件数组
Object[] queryObject = getQueryObject(params);
//执行查询,设置rowMapper进行结果从List<Map> --> List<Object>的转换
return getJdbcTemplate().query(sql, queryObject,new RowMapper(){
public Object mapRow(ResultSet resultSet, int rowNum) throws SQLException {
//~~~ return value 组装返回对象
Object object = getObjectFromResultSet(resultSet,returnClass);
return object;
}
});
}
@SuppressWarnings({ "rawtypes","unchecked" })
private List<Map<String,Object>> queryForMapList(String sql,Map<String,Object> params,final Class objectClass){
//获取查询参数
Object[] queryObject = getQueryObject(params);
//获取对象声明的属性
final Field[] fields = getFieldsFromClass(objectClass);
return (List<Map<String,Object>>)getJdbcTemplate().query(sql, queryObject, new RowMapper(){
public Object mapRow(ResultSet resultSet, int count) throws SQLException {
try{
Map<String,? extends Object> resultMap = getResultMapFromResultSet(resultSet,fields);
return resultMap;
}catch(Exception e){
throw new RuntimeException("can not get result from resultSet!");
}
}
});
}
/**
* 获取注解中配置的表名称
* @param Class 注解所添加位置的类
* @return String 注解中配置的tablename
*/
protected String getTableConfigName(@SuppressWarnings("rawtypes") Class objectClass){
@SuppressWarnings("unchecked")
Table tableAnnotation = (Table)objectClass.getAnnotation(Table.class);
String tableName = tableAnnotation.tableName();
return tableName;
}
/**
* 根据class和resultSet填充对象
* 通过反射,调用setter方法设置DO
* @param resultSet 查询返回值
* @param returnClass 返回值类型
*/
private Object getObjectFromResultSet(ResultSet resultSet,@SuppressWarnings("rawtypes") Class returnClass) throws RuntimeException{
// ~~~ return value
Object object = getInstanceByClass(returnClass);
if(null != object){
if(null == resultSet){
return null;
}
// 根据返回类型获取fields
Field[] fields = getFieldsFromClass(returnClass);
try{
//获取结果map<属性名,属性值>,用于方便的获取属性对应的值
Map<String,? extends Object> resultMap = getResultMapFromResultSet(resultSet,fields);
//遍历列表设置值,通过反射调用setter方法设置
for(Field field : fields){
if(checkIsSerivalVersioinUID(field.getName())){
//如果是序列ID,掠过
continue;
}
// 获取方法名
String methodName = getSetMethod(field.getName());
// 获取方法
Method method = object.getClass().getMethod(methodName, field.getType());
// 获取参数
Object param = resultMap.get(field.getName());//不能直接从resultSet中取值,因为其中的name是数据库的列名
// 执行setter方法
method.invoke(object, param);
}
}catch(Exception e){
logger.error("queryForObject exception!",e);
throw new RuntimeException(e);
}
}else {
//获取实例失败,这里抛出异常
throw new RuntimeException("build result object failed!");
}
return object;
}
/**
* 根据传入Map获取查询参数数组对象
* @param params 参数Map
* @return Object[]
*/
private Object[] getQueryObject(Map<String,Object> params){
// 查询对象,存储查询条件数组
Object[] queryObject = new Object[params.size()];
// 获取查询参数
int index = 0;
// 初始化查询对象
for(String param : params.keySet()){
queryObject[index++] = params.get(param);
}
return queryObject;
}
/**
* 组装查询语句
* <p>
* 目前只支持了全部列查询
* 关于排序列(可变长度参数部分):
* @see queryForListOrderRow
* </p>
* @param rows 需要查找的列,如果null,则全字段
* @param params 参数map
* @param offset 偏移量,-1标示不分页
* @param pagesize 分页大小,-1标示不分页
* @param useValue 是否使用参数值作为搜索条件的值,若false,则使用?代替
* @return string 查询sql
*/
private String buildQuerySql(List<String> rows,Map<String,Object> params,int offset,int pagesize,
boolean useValue,Map<String,String>... orders){
// 获取需要查找的列
String listStr = getSelecteKeyFromList(rows);
// 组装sql
String sql = SQL_SELECT+listStr+ SQL_FROM + tableName;
//如果后面不含有查询条件,则直接返回
if(null!=params && 0 < params.size()){
sql = getSqlCondition(sql,params, useValue);
}
sql = getSqlOrder(sql,orders);
if(-1 != offset || -1 != pagesize){
// limit offset,pagesize
sql += (SQL_LIMIT+offset+SQL_SPLIT+pagesize+" ");
}
return sql;
}
/**
* 组装根据条件查询总记录数的sql
* @param params 条件
* @return sql语句
*/
private String buildQueryConteSql(Map<String,Object> params){
// 获取需要查找的列
String sql = SQL_SELECT+SQL_COUNT+SQL_FROM+ tableName;
//如果后面不含有查询条件,则直接返回
if(null!=params && 0 < params.size()){
sql = getSqlCondition(sql,params, false);
}
return sql;
}
/**
* 从查询列的列表中取出列名,并按照sql的要求逗号隔开,如 a,b,c,d
* 如果传入查询列的列表为空,或者长度为0,怎返回 * ,代表全部字段都查询
* @param rows
* @return
*/
private static String getSelecteKeyFromList(List<String> rows){
if(null == rows || 0 == rows.size()){
return "*";
}
String listStr = rows.toString();
listStr = getSubString(listStr, 1, 1);
return listStr;
}
/**
* 组装更新语句
* 除了主键外的全字段更新,限制条件可以自由设定
* @param object 更新对象
* @param useValue 是否使用具体值替代占位符 ?
* @param conditions 查询条件
* @return 组装后的sql
*/
private String buildUpdateObjectSql(Object object,boolean useValue,Map<String,Object> conditions){
//组装更新sql
String sql = SQL_UPDATE + tableName + SQL_SET;
//获取域数组
Field[] fields = getFieldFromObject(object);
//获取属性和值的map,不包括主键和序列ID
Map<String, Object> params = getParamMap(object, fields);
//更新sql必须含有需要修改的字段的内容,否则抛出异常
if(null == params || 0 >= params.size()){
throw new RuntimeException("can not update without values!");
}
//添加修改字段的sql片段,set的顺序和DO类中的顺是相同的,以便于后续batch操作
for(Field field : fields){
String rowName = field.getName();
if(params.containsKey(rowName)){
sql += (rowName + SQL_EQUAL +(useValue? (StringUtils.isBlank(params.get(rowName).toString())? "null":"'"+params.get(rowName)+"'"):SQL_PLACE_HOLDER));
sql += SQL_SPLIT;
}
}
int lastD = sql.lastIndexOf(',');
sql = getSubString(sql,0,sql.length()-lastD);//去掉最后的一个逗号
//Be Attention!!! 这里需要使用具体值替代占位符,因为批量更新的时候,这个不需要变更,在setValue重载方法中可以省略设置操作
return getSqlCondition(sql,conditions, true);
}
/**
* 获取sql语句的后半部分,从where开始的条件设定部分
* @param sql sql前半部分,可以是select或者update或者delte,不限
* @param conditions 条件map
* @param useValue 是否使用具体值替代占位符?
* @return
*/
private static String getSqlCondition(String sql,Map<String,Object> conditions,boolean useValue){
//开始组装条件
sql += SQL_WHERE;
for(String key : conditions.keySet()){
sql += (key+SQL_EQUAL);
sql += useValue? "'"+conditions.get(key)+"'":SQL_PLACE_HOLDER;
sql += SQL_AND;
}
return getSubString(sql,0, 5);//取消最后的" and ";
}
/**
* 获取sql的后半order by的部分,如果没有指定排序,那么直接返回sql
* @param sql sql前半部分,通常截止于where语句
* @param orders 排序列的map,按照排序的次序依次排列
* @return 转换后的sql
*/
private static String getSqlOrder(String sql,Map<String,String>... orders){
if(null == orders || 0 == orders.length){
return sql;
}else {
sql += SQL_ORDER_BY;
}
//遍历order组,添加附加的排序条件
for(int i = 0; i < orders.length; i++){
//获取其中的每一组,并获取其中的key和value
Map<String,String> orderItem = orders[i];
if(!orderItem.keySet().isEmpty()){
//使用迭代器取出map中的第一个键值对,通常也是当前map中的唯一一对
Iterator<String> index = orderItem.keySet().iterator();
if(index.hasNext()){
String key = index.next();
sql += (" " + key + " "+orderItem.get(key));//like name desc
sql += SQL_SPLIT;//like name desc,
}
}
}
int splitPoint = sql.lastIndexOf(',');
sql = getSubString(sql,0, sql.length()-splitPoint);//去掉最后一个逗号
return sql;
}
/**
* 获取部分字符串
* @param str
* @param begin 起始位数,从首位开始计数,0开始
* @param end 从末尾开始计数的,截止位
* @return
*/
private static String getSubString(String str,int begin,int end){
return str.substring(begin, str.length()-end);
}
/**
* 从对象中获取Field数组
* @param object
* @return
*/
private Field[] getFieldFromObject(Object object){
@SuppressWarnings("rawtypes")
Class objectclass = object.getClass();
Field[] fields = getFieldsFromClass(objectclass);
return fields;
}
/**
* 获取结果的map<fieldName,fieldValue>
* <p>
* 从数据库中获取而来的ResultSet,类型为 <数据库字段相同大小写的字符串,数据库字段类型>
* 我们需要转换成:<对象中的属性名称大小写的字符串,对象中的属性的类型>
* </p>
* @param resultSet 结果set
* @param fields 属性数组
* @throws Exception
*/
private Map<String,? extends Object> getResultMapFromResultSet(ResultSet resultSet,Field[] fields) throws Exception{
//~~~ return value
Map<String,Object> map = new HashMap<String,Object>();
//获取元数据
ResultSetMetaData setMetaData = resultSet.getMetaData();
//遍历各列,获取各列的值和在DO类中对应的属性的名称
for(int i=1;i<=setMetaData.getColumnCount();i++){
//获得列名称
String columnName = setMetaData.getColumnName(i);//特别注意,这里的下标从1开始
if(logger.isDebugEnabled()){
logger.debug("get column:"+columnName);
}
//获得当前列的值
Object rowObject = resultSet.getObject(columnName) == null ? "":resultSet.getObject(columnName);
//获取当前列对应的属性在属性组中的下标
int index = getFieldIndexOfObject(fields,columnName);
if(-1 == index){
throw new Exception("can not find index of column :"+columnName+" in fields.");
}
//将当前字段从数据库类型转换成DO中的类型
rowObject = ConvertUtils.convert(rowObject.toString(), fields[index].getType());
//获得当前列在对象中对应的属性名
String realName = getFieldRealNameOfObject(fields,columnName);
if(null == realName || "".equals(realName)){
logger.error("no field match the name:"+columnName);
throw new Exception();
}
map.put(realName, rowObject);
}
return map;
}
/**
* 用于获取fields中当前数据库列对应的名字
* @param fields 属性域数组
* @param rowNameInResultSet
* @return
*/
private String getFieldRealNameOfObject(Field[] fields,String rowNameInResultSet){
List<String> fieldNameSet = getFiledNameList(fields);
//一次遍历,不分大小写,用于解决DO和数据库字段的属性名字大小写不一致的问题
for(String s:fieldNameSet){
if(StringUtils.equalsIgnoreCase(s, rowNameInResultSet)){
return s;
}
}
return null;
}
/**
* 返回当前列对应的属性在field数组中的下标
* @param fields 对象含有的filed数组
* @param rowNameInResultSet 字段名,可以使数据库中的名称也可以是属性名称
* @return
*/
private int getFieldIndexOfObject(Field[] fields,String rowNameInResultSet){
List<String> fieldNameSet = getFiledNameList(fields);
//一次遍历,不分大小写,用于解决DO和数据库字段的属性名字大小写不一致的问题
int index = 0;
for(String s:fieldNameSet){
if(StringUtils.equalsIgnoreCase(s, rowNameInResultSet)){
return index;
}
index++;
}
return -1;
}
/**
* 根据class获取一个实例对象
* @param class
* @return null 或者对象实例
*/
private Object getInstanceByClass(@SuppressWarnings("rawtypes") Class className){
Object object = null;
try {
object = className.newInstance();//new 一个对象
//把产生的异常都吃掉,这里返回null可以触发上层抛出异常
} catch (InstantiationException e) {
logger.error("new instance exception!",e);
} catch (IllegalAccessException e) {
logger.error("can not access the instance method!",e);
}
//return null or a object
return object;
}
/**
* 根据类名获取所有声明的属性列表
* 注意其中也包含了不属于数据库对应字段的属性,比如序列号ID
* @param className 类名称
* @return field数组
*/
private static Field[] getFieldsFromClass(@SuppressWarnings("rawtypes") Class className){
Field[] fields = className.getDeclaredFields();
return fields;
}
/**
* 根据类名获取除了SERIALVERSIONUID外的属性列表
* @param className 类名称
* @param isIncludeKeyId 是否包含id
* @return field数组
*/
private Field[] getFieldsMatchedDBFromClass(@SuppressWarnings("rawtypes") Class className,boolean isIncludeKeyId){
Field[] fields = getFieldsFromClass(className);
Field[] newfields = new Field[isIncludeKeyId? fields.length-1:fields.length-2];//如果不包含主键,则比原来少两位
int index = 0;
for(Field f : fields){
if(isIncludeKeyId? !checkIsSerivalVersioinUID(f.getName()):checkIsNeedField(f.getName())){
newfields[index++] = f;
}
}
return newfields;
}
/**
* 根据属性数组获取属性名称的set
* 这里必须使用list,以保持次序
* @param fields
* @return
*/
private static List<String> getFiledNameList(Field[] fields) {
//~~~ return value
List<String> fieldNameSet = new ArrayList<String>();
// get the name of each
for (int i = 0; i < fields.length; i++) {
fieldNameSet.add(fields[i].getName());
}
return fieldNameSet;
}
/**
* 设置需要增加的属性以及主键
* 针对于insert方法,提供需要插入的字段名称,其中不包括主键ID和序列号SerivalVserionId
* @param fieldNameSet
*/
private void setUsingListFromNameSet(SimpleJdbcInsert inserActor,List<String> fieldNameSet) {
//~~~ return value
List<String> usingColums = new ArrayList<String>();
// 使用所有的除了主键和序列号的字段名称
for (String field : fieldNameSet) {
if (StringUtils.equalsIgnoreCase(field,
DEFAULT_SERIALVERSIONUID_NAME)) {
//序列号掠过
continue;
}
if (!StringUtils.equalsIgnoreCase(field, DEFAULT_ID_NAME)) {
//不是主键,放入字段列表
usingColums.add(field);
} else {
//主键作为返回字段
inserActor = inserActor.usingGeneratedKeyColumns(field);
}
}
inserActor.setColumnNames(usingColums);
}
/**
* 检查是否是序列号ID
* @param field
* @return
*/
private boolean checkIsSerivalVersioinUID(String field){
return StringUtils.equalsIgnoreCase(field,DEFAULT_SERIALVERSIONUID_NAME);
}
/**
* 获取插入数据库中的sql需要的参数Map
* 其中不包括主键和序列号
* @param object 实例化后的对象
* @param fields 对象类包含的class
* @return <属性名称,属性值>
* @throws SecurityException
*/
private Map<String, Object> getParamMap(Object object, Field[] fields) throws SecurityException{
//~~~ return value
Map<String, Object> paramMap = new HashMap<String, Object>();
//依次遍历对象的get方法,获取对象的属性值,放入map中
for (int i = 0; i < fields.length; i++) {
if (!checkIsNeedField(fields[i].getName())) {
continue;
} else {
String value = getValueByName(object,fields[i].getName());
paramMap.put(fields[i].getName(), value);
}
}
return paramMap;
}
/**
* 根据名称获取对象中的值
* @param object 对象
* @param name 属性名
* @return
* @throws RuntimeException
*/
private String getValueByName(Object object,String name) throws RuntimeException{
String methodName = getGetMethod(name);
if(logger.isDebugEnabled()){
logger.debug("run method:" + methodName);
}
try {
// 通过代理执行访问器
Method method = object.getClass().getMethod(methodName); // get方法没有参数,不需要获取参数类型,fields[i].getGenericType().getClass()
Object result = method.invoke(object, new Object[] {});//调用get方法获取属性值
String value = null==result? "":result.toString();
return value;
} catch (SecurityException e) {
// 由安全管理器抛出的异常,指示存在安全侵犯
throw e;
} catch (NoSuchMethodException e) {
// 没有该方法
logger.error("There is no method '"+methodName+"' in Class "+object.getClass()+"! Check your getter method name!",e);
return null;
}catch (IllegalArgumentException e) {
// 参数不正确
logger.error("IllegalArgumentException happend because the method '"+methodName+"'"+"' in Class "+object.getClass()+ "has some parameters!",e);
return null;
} catch (IllegalAccessException e) {
// 访问的方法不能被访问,一般是由于private等权限问题
logger.error("The method '"+methodName+"' in Class "+object.getClass()+"can not be accessed!Check your getter method whether it is private of protected!",e);
return null;
} catch (InvocationTargetException e) {
// 映射目标异常
logger.error("The invoke method '"+methodName+"' in Class "+object.getClass()+"is not right!Check your getter method name!",e);
return null;
}
}
/**
* 获取默认的get方法的名字,根据spring的默认规则
* @param fieldName
* @return
*/
private String getGetMethod(String fieldName){
//生成诸如getParam1的方法名称
String methodName = GET_METHOD_PRE + changeFirstChar2upper(fieldName);
return methodName;
}
private String getSetMethod(String fieldName){
//生成诸如setParam1的方法名称
String methodName = SET_METHOD_PRE + changeFirstChar2upper(fieldName);
return methodName;
}
/**
* 将字符串的首字母大写
* @param fieldName
* @return
*/
private static String changeFirstChar2upper(String fieldName){
return fieldName.toUpperCase().charAt(0) + fieldName.substring(1, fieldName.length());
}
/**
* 检查是否是必要的需要修改的字段
* @param fieldName
* @return
*/
private boolean checkIsNeedField(String fieldName){
if (StringUtils.equalsIgnoreCase(fieldName, DEFAULT_SERIALVERSIONUID_NAME)
|| StringUtils.equalsIgnoreCase(fieldName, DEFAULT_ID_NAME)) {
return false;
}
return true;
}
/***************************************///getter & setter //***********************************************
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
}
分页以及table自定义注解代码 (略) 请参见前面章节。
部分代码重构过以提高复用率,截止目前以此版本为准。