兴趣使然之封装Spring的JdbcTemplate
使用过几个ORM框架,都感觉并不合适我使用,SpringData与Hibernate虽然强大,但是太多功能我日常工作或者学习上并不需要,且在调优问题上有一定的阻碍,而MyBatis又需要把sql写到xml文件或者注解里面,而我更喜欢sql直接写到代码里面用Java代码写逻辑控制sql语句。就想着封装一个简单方便适合自己日常学习使用的ORM框架。相比直接封装原生的JDBC,Spring提供的JdbcTemplate工具类已经提供了一条捷径,所以这次简单封装一下JdbcTemplate,使平时学习能更加顺手
先展示一下使用(下面用的都是main函数测试,因为不想引入太多依赖导致后面我学习使用时还要再删除):
下面是上面调用的testSave()函数
下面是上面调用的save()函数
save()执行完以后数据库多了条数据并且把执行的sql打印出来了(打印用的是System.out.println打印,上图截完才加上的),我的数据库id是自增的,所以插入的时候没有设置值,又把我刚封装好时测试的数据删除了,所以就从11开始
下面内容仅用于记录封装,不做太详细说明
一,映射注解
ORM框架都是由对象与数据映射关系组成,为了标明某个对象映射某个表的,对象的字段对应表的哪个字段,所以先写了2个注解 @Column 与 @Table
@Table仅用于注明是映射哪个表
@Column用于注明映射哪个字段,及是否为主键
/** * 用于标识类对应数据库的表*/@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value() default "";
}
/** * 用于标识字段对应数据库的列名称*/@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String value() default "";
boolean isKey() default false;
}
下面是使用方式,getXxx()和setXxx()函数就不复制出来了,另外最后一个test字段没使用@Column则不进行映射关系,其他用到注解的地方都说明了对应的是哪个表,哪个字段,如果是联合主键则多字段使用 isKey=true
@Table("sys_user")public class User implements Serializable {
public final static String TABLE_USER="sys_user";
@Column(value = "id",isKey = true)
private Long id;
@Column("user_name")
private String userName;
@Column("password")
private String password;
@Column("nick_name")
private String nickName;
@Column("gender")
private Integer gender;
@Column("mobile")
private String mobile;
@Column("email")
private String email;
private String test;
二,根据映射对象生成DML语句
编写了一个DaoUtils的泛型类,这样就可以根据各种对象动态生成sql
里面有13个函数(一个main方法,前期测试使用),1个属性
table属性:主要用于存储通过analysisTable(T t)函数反射获取到的表信息与字段信息
/** * 储存表信息与字段信息*/private Map<String,Object> table;
analysisTable(T t):通过反射获取到的表信息和字段信息
下面是table属性储存的属性
tableName:存储表名
tableColumns:储存表字段信息
keyColumns:储存主键信息
/** * 根据对象获取表信息,表名,字段名,字段值* @return* @throws Exception */private Map<String,Object> analysisTable(T t) throws DtoAnalysisTableException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {Class<?> clazz = t.getClass();
// 对象所有的信息集合,表,字段,字段值
Map<String,Object> result = new HashMap<String,Object>();
// 储存字段信息
Map<String,Object> fieldMap = new TreeMap<String, Object>();
// 储存主键信息
Map<String,Object> keyMap = new TreeMap<String, Object>();
// 获取类名,类的Table注解
Table table = clazz.getAnnotation(Table.class);
if(table == null) {
throw new DtoAnalysisTableException("Object not has Table of Annotation");
}
// 获取所有字段名称
Field[] fields = clazz.getDeclaredFields();
// 遍历每个字段字段的信息
for (Field field: fields) {
// 获取字段映射信息
Column annotation = field.getAnnotation(Column.class);
// 当存在映射信息时候才进行储存
if(annotation != null){
// 获取getXxx()函数
Method getMethod = clazz.getDeclaredMethod(MethodUtils.getMethod(field.getName()));
// 根据getXxx函数获取值
Object invoke = getMethod.invoke(t);
// 储存该数据库字段对应的值
fieldMap.put(annotation.value(), invoke);
// 判断是否主键,如果是则存储到主键信息
if(annotation.isKey()){
keyMap.put(annotation.value(),invoke);
}
}
}
// 添加表名
result.put("tableName", table.value());
// 添加字段信息
result.put("tableColumns", fieldMap);
// 添加主键字段信息
result.put("keyColumns",keyMap);
return result;
}
上面用到的自定义类 MethodUtils 与自定义异常 DtoAnalysisTableException
MethodUtils 主要提供了根据字段名获取getXxx()和setXxx()的函数
/** * 传入一个字段名,获取该字段的* getXxx() * @param fieldName* @return*/public static String getMethod(String fieldName){if(fieldName == null || fieldName.length() < 1){
return "";
}
return "get"+fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
}
/** * 传入一个字段名,获取该字段的* setXxx() * @param fieldName* @return*/public static String setMethod(String fieldName){if(fieldName == null || fieldName.length() < 1){
return "";
}
return "set"+fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
}
DtoAnalysisTableException这是该类没有表映射关系的异常
/** * 数据库映射异常*/public class DtoAnalysisTableException extends Exception{public DtoAnalysisTableException(String message) {
super(message);
}
}
接着说回DaoUtils的函数
getTable(T t) 是获取 table的函数,避免同一DaoUtils对象多次调用analysisTable(T t)浪费资源
/** * 用于获取类映射的表信息* @param t* @return*/private Map<String, Object> getTable(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException {if(table == null) {
table = analysisTable(t);
}
return table;
}
insertCols() 与 updateCols()则是获取的 sql 的部分语句 例如下面图片红圈出:
/** * 根据getTable()返回的集合 tableColumns 获取持久化对象的属性名字符串* @param colMap* @return*/private String insertCols(Map<String,Object> colMap){StringBuffer insertCols = new StringBuffer("");
// 获取key(表字段名)
Set<String> keySet = colMap.keySet();
// 遍历key获取对应的数据库,拼接部分sql语句,例如:" fieldName1,fieldName2,fieldName3 "
for (String col : keySet) {
if(insertCols.length() > 0){
insertCols.append(",");
}
insertCols.append(col);
}
return insertCols.toString();
}
/**
* 根据getTable()返回的集合 tableColumns 获取持久化对象的属性名字符串
* @param colMap
* @return
*/
private String updateCols(Map<String,Object> colMap){
StringBuffer insertCols = new StringBuffer("");
// 获取key(表字段名)
Set<String> keySet = colMap.keySet();
// 遍历key获取对应的数据库,拼接部分sql语句,例如:"fieldName1=?,fieldName2=?,fieldName3=? "
for (String col : keySet) {
if(insertCols.length() > 0){
insertCols.append(",");
}
insertCols.append(col).append("=?");
}
return insertCols.toString();
}
下面的 insertSql(T t) 、insertBatchSql(T t,int size)、updateSql(T t)、deleteSql(T t) 则是获取插入、批量插入、根据主键修改、根据主键删除的SQL
/** * 根据表信息,获取insert sql语句* @return*/public String insertSql(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException {// 反射获取实体内容
Map<String, Object> tableByDto = getTable(t);
// 获取表名
String tableName = tableByDto.get("tableName")+"";
// 获取表字段
Map<String, Object> colMap = (Map<String, Object>) tableByDto.get("tableColumns");
// 拼接sql
StringBuffer sql = new StringBuffer(" INSERT INTO ");
// 获取 "" 这样的内容并拼接
sql.append(tableName).append(" (").append(insertCols(colMap)).append(") ");
// 获取 "?,?,?,?,?" 这样的内容并拼接
sql.append(" VALUES ").append(" (").append(insertParam(colMap)).append(") ");
return sql.toString();
}
/**
* 根据表信息,批量获取insert sql语句
* @param size 批量的数量
* @return
*/
public String insertBatchSql(T t,int size) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException {
// 反射获取实体内容
Map<String, Object> tableByDto = getTable(t);
// 获取表字段
Map<String, Object> colMap = (Map<String, Object>) tableByDto.get("tableColumns");
StringBuffer sql = new StringBuffer(insertSql(t));
// 获取 "?,?,?,?,?" 这样的内容
String insertParam = insertParam(colMap);
// 拼接sql
for (int i = 0 ; i < size - 1; i++){
sql.append(" , (").append(insertParam).append(") ");
}
return sql.toString();
}
/**
* 根据表信息,获取update sql语句
* @return
*/
public String updateSql(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException {
// 反射获取实体内容
Map<String, Object> tableByDto = getTable(t);
// 获取表字段
Map<String, Object> colMap = (Map<String, Object>) tableByDto.get("tableColumns");
// 获取表主键
Map<String, Object> keyMap = (Map<String, Object>) tableByDto.get("keyColumns");
// 获取标名
String tableName = tableByDto.get("tableName")+"";
Set<String> keys = keyMap.keySet();
// 拼接UPDATE SQL
StringBuffer sql = new StringBuffer(" UPDATE ");
sql.append(tableName).append(" set ").append(updateCols(colMap)).append(" ");
sql.append(" where ");
int i = 0;
for (String key : keys) {
sql.append(key).append("=? ").append((i++ < keys.size()-1) ? " and " : "");
}
return sql.toString();
}
/**
* 根据表信息,获取根据主键delete sql语句
* @return
*/
public String deleteSql(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException {
// 反射获取实体内容
Map<String, Object> tableByDto = getTable(t);
// 获取表名
String tableName = tableByDto.get("tableName")+"";
// 获取主键
Map<String, Object> keyMap = (Map<String, Object>) tableByDto.get("keyColumns");
Set<String> keys = keyMap.keySet();
// 拼接sql
StringBuffer sql = new StringBuffer(" DELETE from ");
sql.append(tableName).append(" WHERE ");
int i = 0;
for (String key : keys) {
sql.append(key).append("=? ").append((i++ < keys.size()-1) ? " and " : "");
}
return sql.toString();
}
上面有使用到 insertParam() 函数 主要是用于获取插入语句的 “?,?,?,?” 这样的内容
/** * 获取参数字符串 格式:?,?,?,? * @param colMap* @return*/private String insertParam(Map<String,Object> colMap){StringBuffer param = new StringBuffer("");
for (int i = 0;i < colMap.size(); i++){
param.append("?");
if(i < colMap.size()-1){
param.append(",");
}
}
return param.toString();
}
再接下来是获取使用 JdbcTemplate 的接口时候需要使用的参数值。insertValues(T t)、insertBatchValues(List<T> list)、updateValues(T t)、deleteValues(T t) 对应的分别是获取插入、批量插入、根据主键修改和根据主键删除的函数
/** * 根据getTable()返回的集合 tableColumns 获取对象的属性值* @param t* @return*/public Object[] insertValues(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException {// 获取表信息
Map<String, Object> table = getTable(t);
// 获取表字段
Map<String, Object> colMap = (Map<String, Object>) table.get("tableColumns");
Object[] values = new Object[colMap.size()];
// 根据字段名获取值
Set<String> keySet = colMap.keySet();
int i = 0;
for (String col : keySet) {
values[i++] = colMap.get(col);
}
return values;
}
/**
* 根据getTable()返回的集合 tableColumns 获取对象的属性值
* @return
*/
public Object[] insertBatchValues(List<T> list) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException {
// 用于储存所有参数
List<Object> values = new ArrayList<Object>();
// 变量获取参数
for (int i = 0;i < list.size(); i++) {
// 调用insertValues获取该对象的参数值
Object[] objects = insertValues(list.get(i));
// 把参数添加到列表
values.addAll(Arrays.asList(objects));
}
return values.toArray();
}
/**
* 根据getTable()返回的集合 tableColumns 获取对象的属性值
* @param t
* @return
*/
public Object[] updateValues(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException {
// 获取表信息
Map<String, Object> table = getTable(t);
// 获取所有表字段
Map<String, Object> colMap = (Map<String, Object>) table.get("tableColumns");
// 获取主键字段
Map<String, Object> keyMap = (Map<String, Object>) table.get("keyColumns");
List<Object> values = new ArrayList<Object>();
// 获取所有字段
Set<String> keySet = colMap.keySet();
// 根据key获取对应的值
for (String col : keySet) {
values.add(colMap.get(col));
}
// 获取主键字段
Set<String> keys = keyMap.keySet();
// 根据key获取对应的值
for (String key : keys) {
values.add(colMap.get(key));
}
return values.toArray();
}
/**
* 根据getTable()返回的集合 tableColumns 获取对象的属性值
* @param t
* @return
*/
public Object[] deleteValues(T t) throws NoSuchMethodException, IllegalAccessException, DtoAnalysisTableException, InvocationTargetException {
// 获取表信息
Map<String, Object> table = getTable(t);
// 获取字段信息
Map<String, Object> colMap = (Map<String, Object>) table.get("tableColumns");
// 获取主键信息
Map<String, Object> keyMap = (Map<String, Object>) table.get("keyColumns");
// 获取key(表属性)
Set<String> keys = keyMap.keySet();
List<Object> values = new ArrayList<Object>();
// 根据key获取对应的值
for (String key : keys) {
values.add(colMap.get(key));
}
return values.toArray();
}
对于这个类获取SQL的测试,使用main()调用
// 测试使用public static void main(String[] args) {try{
User user = new User();
user.setUserName("11111");
user.setPassword("fdsfds");
user.setMobile("137");
user.setEmail("email");
// 添加sql
String insertSql = new DaoUtils<User>().insertSql(user);
System.out.println(insertSql);
// 修改sql
String updateSql = new DaoUtils<User>().updateSql(user);
System.out.println(updateSql);
// 修改sql
String deleteSql = new DaoUtils<User>().deleteSql(user);
System.out.println(deleteSql);
// 批量添加sql
String insertBatchSql = new DaoUtils<User>().insertBatchSql(user,3);
System.out.println(insertBatchSql);
} catch (Exception e){
e.printStackTrace();
}
}
打印结果:
下面就是封装好的 BaseDao
public class BaseDao<T> {protected JdbcTemplate jdbcTemplate;
/**
* 插入函数
* @param t
*/
public void save(T t) throws InvocationTargetException, NoSuchMethodException, DtoAnalysisTableException, IllegalAccessException {
// 创建Util对象
DaoUtils<T> du = new DaoUtils<T>();
// 获取sql语句
String s = du.insertSql(t);
// 获取sql参数
Object[] params = du.insertValues(t);
// 调用JdbcTemplate 执行
jdbcTemplate.update(s,params);
}
/**
* 修改函数
* @param t
*/
public void update(T t) throws InvocationTargetException, NoSuchMethodException, DtoAnalysisTableException, IllegalAccessException {
// 创建Util对象
DaoUtils<T> du = new DaoUtils<T>();
// 获取sql语句
String s = du.updateSql(t);
// 获取sql参数
Object[] params = du.updateValues(t);
// 调用JdbcTemplate 执行
jdbcTemplate.update(s,params);
}
/**
* 删除函数
* @param t
*/
public void delete(T t) throws InvocationTargetException, NoSuchMethodException, DtoAnalysisTableException, IllegalAccessException {
// 创建Util对象
DaoUtils<T> du = new DaoUtils<T>();
// 获取sql语句
String s = du.deleteSql(t);
// 获取sql参数
Object[] params = du.deleteValues(t);
// 调用JdbcTemplate 执行
jdbcTemplate.update(s,params);
}
/**
* 批量插入函数
* @param list
*/
public void batchSave(List<T> list) throws InvocationTargetException, NoSuchMethodException, DtoAnalysisTableException, IllegalAccessException {
// 创建Util对象
DaoUtils<T> du = new DaoUtils<T>();
// 获取sql语句
String s = du.insertBatchSql(list.get(0), list.size());
// 获取sql参数
Object[] params = du.insertBatchValues(list);
// 调用JdbcTemplate 执行
jdbcTemplate.update(s,params);
}
// 测试代码
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
当然,最开始只是因为测试,所以直接使用的BaseDao,实际写一个UserDao再继承BaseDao使用更方便,这样可以使用增删改的工具函数,也能定义属于User类自己的sql例如:
public class UserDao extends BaseDao<User> {public List<User> list(){
return jdbcTemplate.query(" select * from " + User.TABLE_USER , new Object[]{},new LocalRowMapper<User>(User.class));
}
}
上面BaseDao的增删改工具函数就不再进行测试了
三,下面开始说说查询
JdbcTemplate 原生的query()是不支持类字段和表字段映射规则不一样的查询操作,例如:数据库字段 user_name 与 类属性 userName 这2个可以映射到,query时会根据下划线"_"的位置进行驼峰命名,但是如果遇到 user_name 与 userName001 这样的字段,则不能通过映射,下面测试一下
这是表的内容
先测试用JdbcTemplate提供的 BeanPropertyRowMapper 执行
查询list函数
测试函数
main()
看看执行内容:证明如果是user_name和userName能够映射
再把我们上面的User类代码的userName改成userName1,
main()执行结果,可以看到userName1的字段是null,并没有映射到:
下面再改成我自己重写RowMapper后的 LocalRowMapper,其他代码不变,把BeanPropertyRowMapper 改成 LocalRowMapper
执行结果,换成了LocalRowMapper之后userName1与user_name也能映射到,userName与user_name更不用说了:
userName1
userName
下面看看 LocalRowMapper 类
/** * 重写RowMapper,使它能够使用@Table 与 @Column * @param <T>*/public class LocalRowMapper<T> implements RowMapper<T>{private Class<?> clazz;
// 保存字段与数据库字段的映射关系
private Map<String,String> fieldMap;
// 保存字段与setXxx()函数的参数类型
private Map<String,Class> fieldTypeMap;
public LocalRowMapper(Class<?> clazz){
try {
// 设置calss
this.clazz = clazz;
// 初始化 fieldMap
this.fieldMap = new HashMap<String, String>();
// 初始化 fieldTypeMap
this.fieldTypeMap = new HashMap<String, Class>();
this.analysisTable();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 重写 RowMapper 的 mapRow() 函数
* @param rs
* @param arg
* @return
*/
public T mapRow(ResultSet rs, int arg) {
T t= null;
try {
// 根据class 实例化对象
t = (T)clazz.newInstance();
// 获取与数据库映射的字段
Set<String> keySet = this.fieldMap.keySet();
for (String key: keySet) {
// 获取setXxx()
Method setMethod = clazz.getDeclaredMethod(MethodUtils.setMethod(key),this.fieldTypeMap.get(key));
// 根据字段名称获取ResultSet返回字段所在下标
// 通过下标获取到对于字段的值
// 执行setXxx() 把值设置到对象
setMethod.invoke(t,rs.getObject(rs.findColumn(this.fieldMap.get(key))));
}
} catch (Exception e) {
e.printStackTrace();
}
return t;
}
/**
* 获取字段映射信息函数
* @throws DtoAnalysisTableException
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
private void analysisTable() throws DtoAnalysisTableException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class<?> clazz = this.clazz;
// 获取所有字段名称
Field[] fields = clazz.getDeclaredFields();
// 遍历每个字段字段的信息
for (Field field: fields) {
Column annotation = field.getAnnotation(Column.class);
if(annotation != null){
// 保存字段与数据库字段
this.fieldMap.put(field.getName(), annotation.value());
// 保存字段与setXxx()函数的参数类型
this.fieldTypeMap.put(field.getName(),field.getType());
}
}
}
}
以上就是对这次 JdbcTemplate 封装的所有内容了,下面是源码链接(OSCHINA不能上传附件只能放云盘了)
https://pan.baidu.com/s/1e0DsZKP_D9dWVcZ4TRayHQ
提取码:awip
以上是 兴趣使然之封装Spring的JdbcTemplate 的全部内容, 来源链接: utcz.com/z/514527.html