每个项目都会用到最基本的增删改查操作,而很多代码都是重复的。所以这篇文章写一下自己在项目中抽取出来的通用代码(基于Mybatis和spring框架,数据库是MySql),其中有一些工具方法是参考别人的。之前看过很多文章的实现方法都是通过字符串拼接Sql语句,然后再mybatis.xml映射文件中直用${sql},如下:
直接在dao方法里面拼接好传过来,这样一个坏处就是不能防止sql注入,在mybatis映射文件中,能用#{}的尽量用#{}(在这里不聊${}与#{}的区别,不清楚两者区别的可以去了解一下)。
1.DAO接口:
**
* 公共数据库操作层
* @param 实体PO类型
* @param PO主键类型
*/
public interface Dao {
/**
* 记录添加
* @param po
* @return
*/
public int add(T po);
/**
* 批量添加
* @param poList
* @return
*/
public int addBatch (List poList);
/**
* 通过主键获取某个记录
* @param id 主键
* @return PO
*/
public T get(PK id);
/**
* 通过主键获取某个字段的值
* @param id
* @param fileName
* @return
*/
public Serializable getField(PK id, String fileName);
/**
* 条件获取一条记录
* @param t
* @param 条件表达式
* @return PO
*/
public T get(Where where);
/**
* 条件获取某个记录字段
* @param where
* @param fileName
* @return
*/
public Serializable getFile(Where where, String fileName);
/**
* 条件查询列表
* @param where 条件表达式
* @return PO列表
*/
public List list(Where where);
/**
* 更新不为null的PO字段
* @param po
* @return 受影响的行数
*/
public int updateLocal(T po);
/**
* 更新PO的所有字段
* @param po
* @return 受影响的行数
*/
public int update(T po);
/**
* 条件更新不为null的字段
* @param po
* @param 条件表达式
* @return 受影响的行数
*/
public int updateLocal(T po, Where where);
/**
* 条件更新所有字段
* @param po
* @param 条件表达式
* @return 受影响的行数
*/
public int update(T po, Where where);
/**
* 删除某个记录
* @param id 主键
* @return 受影响的行数
*/
public int del(PK id);
/**
* 条件删除某个记录
* @param where 条件表达式
* @return 受影响的行数
*/
public int del(Where where);
/**
* 获取指定条件的记录数
* @param where 条件表达式
* @return 查询到的记录数
*/
public long count(Where where);
/**
* 获取对应表中的记录数
* @return 表中的条数
*/
public long count();
public List in(String fileName, Serializable[] values);
/**
* 内查询
* @param fileName 用于内查询的字段
* @param values 字段的值
* @return 查询到的结果集
*/
public List in(String fileName, Serializable[] values,Where where);
}
其中Po为所有实体类的父类,所有要入库的实体类都继承它:
public class Po {
}
比如:Test
public class Test extends Po{
private Integer id;
private String name;
//省去set/get方法
}
而里面有一些方法参数为Where,这个为封装的条件类,在下面详细说。
2.DAO实现类DaoImpl
@SuppressWarnings("unused")
@Repository
public class DaoImpl implements Dao {
protected Logger logger = Logger.getLogger(this.getClass());
@Resource(name = "sqlSessionTemplateASS")
protected SqlSessionTemplate sqlSessionTemplateASS; //sqlSession实例,spring-mybatis.xml配置文件中配置
private Class entityClass;
private String pkName; //主键字段
private String idName; //对应id名称
private String tableName; //表名
private List sqlParms; //实体类参数列表,无值无id
private List selectSqlParms; //查询参数列表 无值有id
private SqlUtil sqlUtil; //工具类
public DaoImpl(){
this("id", "id");
}
/**
*
* @param pkName 主键字段名
* @param idName 对应id名称
*/
@SuppressWarnings("unchecked")
public DaoImpl(String pkName,String idName){
super();
this.sqlUtil = new SqlUtil();
this.entityClass = (Class) GenericsUtils.getSuperClassGenricType(this.getClass());//获取当前类的class
this.sqlParms = this.sqlUtil.getPramList(this.entityClass);//通过class获取该实体类的参数放到Pram
this.selectSqlParms = this.sqlUtil.getPramListOfSelect(this.entityClass);//通过class获取该实体类的参数放到Pram
this.tableName = this.sqlUtil.getTableName(this.entityClass);
this.pkName = pkName;
this.idName = idName;
}
/**
* 添加
*/
@Override
public int add(T po) {
MybatisPo mybatisPo = new MybatisPo(this.pkName,this.idName,this.tableName,
this.selectSqlParms,this.sqlParms);
List pramList = SqlUtil.getPramListofStatic(po);
mybatisPo.setPramList(pramList);
int c = sqlSessionTemplateASS.insert("add", mybatisPo);
SqlUtil.setFileValue(po, idName, (Serializable)mybatisPo.getPkValue());
return c;
}
/**
* 批量添加
*/
@Override
public int addBatch(List poList) {
MybatisPo mybatisPo = new MybatisPo(this.pkName,this.idName,this.tableName,
this.selectSqlParms,this.sqlParms);
List> batchPramList = new ArrayList<>();
for (T po:poList) {
List pramList = SqlUtil.getPramListofStatic(po);
batchPramList.add(pramList);
}
mybatisPo.setBatchPramList(batchPramList);
return sqlSessionTemplateASS.insert("addBatch", mybatisPo);
}
/**
* 通过主键获取某个记录
* @param id 主键
*/
@Override
public T get(PK ID) {
MybatisPo mybatisPo = new MybatisPo(this.pkName,this.idName,this.tableName,
this.selectSqlParms,this.sqlParms);
mybatisPo.setPkValue(ID);
Map resultMap = sqlSessionTemplateASS.selectOne(
"getById", mybatisPo);
return handleResult(resultMap, this.entityClass);
}
/**
* 条件查询列表
*/
@Override
public List list(Where where) {
MybatisPo mybatisPo = new MybatisPo(this.pkName,this.idName,this.tableName,
this.selectSqlParms,this.sqlParms);
mybatisPo.setWhere(where);
List
List
public class Pram {
private String file; //实体属性名
private Object value; //属性对应的值
public Pram(){}
public Pram(String file){
this.file = file;
}
public Pram(String file, Object value){
this.file = file;
this.value = value;
}
//省略set/get方法
}
在这里贴出SqlUtil
public List getPramList(Class po){
List list = new ArrayList<>();
Class extends Po> thisClass = po;
Field[] fields = thisClass.getDeclaredFields();
for(Field f : fields){
if(!f.getName().equalsIgnoreCase("ID") && !f.isAnnotationPresent(TempField.class)){
String fName = f.getName();
if (f.isAnnotationPresent(FieldName.class)) {
String fieldName = f.getAnnotation(FieldName.class).name();
Pram pram = new Pram(fieldName);
list.add(pram);
}else{
String fieldName = toTableString(fName);
Pram pram = new Pram(fieldName);
list.add(pram);
}
}
}
return list;
}
public List getPramListOfSelect(Class po){
List list = new ArrayList<>();
Class extends Po> thisClass = po;
Field[] fields = thisClass.getDeclaredFields();
for(Field f : fields){
if (!f.isAnnotationPresent(TempField.class)) {
String fName = f.getName();
if (f.isAnnotationPresent(FieldName.class)) {
String fieldName = f.getAnnotation(FieldName.class).name();
Pram pram = new Pram(fieldName + " as " + fName);
list.add(pram);
}else{
String fieldName = toTableString(fName);
Pram pram = new Pram(fieldName + " as " + fName);
list.add(pram);
}
}
}
return list;
}
public String getTableName(Class po){
if(po.isAnnotationPresent(TableName.class)){
return po.getAnnotation(TableName.class).name();
}else{
String tName = toTableString(po.getSimpleName());
String poName = tName.substring(tName.length() - 2, tName.length());
if("po".equals(poName)){
tName = tName.substring(0,tName.length() - 3);
}
return tName;
}
}
public static List getPramListofStatic(Po po){
List list = new ArrayList<>();
Class extends Po> thisClass = po.getClass();
Field[] fields = thisClass.getDeclaredFields();
try {
for(Field f : fields){
if(!f.getName().equalsIgnoreCase("ID") && !f.isAnnotationPresent(TempField.class)){
String fName = f.getName();
String getf = "get";
String fieldType = f.getGenericType().toString();
if (fieldType.indexOf("boolean") != -1 || fieldType.indexOf("Boolean") != -1) {
getf = "is";
}
if (f.isAnnotationPresent(FieldName.class)) {
String fieldName = f.getAnnotation(FieldName.class).name();
Method get = thisClass.getMethod(getf + fName.substring(0, 1).toUpperCase() + fName.substring(1));
Object getValue = get.invoke(po);
Pram pram = new Pram(fieldName, getValue);
list.add(pram);
}else{
String fieldName = new SqlUtil().toTableString(fName);
Method get = thisClass.getMethod(getf + fName.substring(0, 1).toUpperCase() + fName.substring(1));
Object getValue = get.invoke(po);
Pram pram = new Pram(fieldName, getValue);
list.add(pram);
}
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return list;
}
/**
* 将某个实体通过反射强制赋给实体某个域
* @param po
* @param fileName
* @param fileValue
* @return
*/
public static boolean setFileValue(Po po, String fileName, Serializable fileValue){
fileName = UnderlineToHump(fileName);
Class extends Po> thisClass = po.getClass();
try {
Field fie ld = thisClass.getDeclaredField(fileName);
String calssName = field.getType().getName();
if (calssName.equals("int") || calssName.equals("java.lang.Integer")) {
if (Integer.MAX_VALUE > new Integer("" + fileValue)) {
Integer val = new Integer("" + fileValue);
Method method = thisClass.getMethod("set" + fileName.substring(0, 1).toUpperCase() + fileName.substring(1), field.getType());
method.invoke(po, val);
return true;
}else{
throw new RumtimeException();
}
}else if(calssName.equals("long") || calssName.equals("java.lang.Long")){
Long val = new Long("" + fileValue);
Method method = thisClass.getMethod("set" + fileName.substring(0, 1).toUpperCase() + fileName.substring(1), field.getType());
method.invoke(po, val);
return true;
}else{
Method method = thisClass.getMethod("set" + fileName.substring(0, 1).toUpperCase() + fileName.substring(1), field.getType());
method.invoke(po, fileValue);
return true;
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return false;
}
/**
* 将驼峰标识转换为下划线
* @param text
* @return
*/
public static String toTableString(String text){
if (text==null||"".equals(text.trim())){
return "";
}
StringBuilder sb=new StringBuilder(text);
int temp=0;//定位
for(int i=0;i
在DaoImpl实现方法中,都是根据每个方法构造所需的MybatisPo,MybatisPo是传到dao-mapper.xml中sql方法的参数,如下:
public class MybatisPo{
private String pkName; //主键字段
private String idName; //对应id名称
private Object pkValue; //主键值
private String tableName;
private List sqlParms; //无值无id 插入单个时的插入列
private List selectSqlParms; //无值有id 查询参数列表
private List pramList; //插入列表值 有值无id
private List> batchPramList; //批量插入的时候用到
private Where where; //sql条件
public MybatisPo() {}
public MybatisPo(String pkName,String idName,String tableName,List selectSqlParms,List sqlParms) {
this.pkName = pkName;
this.idName = idName;
this.tableName = tableName;
this.selectSqlParms = selectSqlParms;
this.sqlParms = sqlParms;
}
//省略set/get方法
}
这个类包含下面第3点写到的dao-mapper.xml中构造的通用方法所需的参数。
Sql条件类Where封装了dao-mapper.xml中条件语句,所以在业务方法中需要构造所需的Where来完成自定义条件的查询或更新等功能。Where:
/**
* 条件类
* @author Administrator
*
*/
public class Where {
private String limit; //对应mysql的limit语句
private String orderBy; //排序条件
private List list = new ArrayList(); //对应sql语句中where后面的条件
public Where(){}
/**
*
* @param file 字段名 如:name
* @param where 条件符号 如:=
* @param value 条件值 如:hehe,就相当于想添加一个 name='hehe'的条件
*/
public Where(String file, String where, Serializable value){
file = SqlUtil.toTableString(file);
WherePo po = new WherePo(file,where,(String)(value+""));
this.list.add(po);
}
//and一个条件,Global.WHERE_AND是一个字符串:"and",下面还有对于的"or"
public Where and (String file,String where, Serializable value){
file = SqlUtil.toTableString(file);
this.list.add(new WherePo(file, where, (String)(value+""), Global.WHERE_AND));
return this;
}
//or 一个条件
public Where or (String file,String where, Serializable value){
file = SqlUtil.toTableString(file);
this.list.add(new WherePo(file, where, (String)(value+""), Global.WHERE_OR));
return this;
}
public Where limit(int startNum, int length){
this.limit = String.format("limit %s,%s", startNum,length);
return this;
}
/**
* 添加排序规则 order by
* @param file 字段
* @param sequence 排序规则 desc/asc
* @return
*/
public Where orderBy(String file,String sequence){
this.orderBy = "order by ";
if (sequence == null) {
sequence = "";
}
this.orderBy += String.format("%s %s,", file , sequence);
return this;
}
/**
* 默认asc排序
* @param file
* @return
*/
public Where orderBy(String fileName){
fileName = SqlUtil.toTableString(fileName);
this.orderBy(SqlUtil.toTableString(fileName), Global.SEQ_ASC);
return this;
}
}
where后面可跟多个条件,所以每个条件用一个WherePo来表示:
/**
* 条件实体类
* @author Administrator
*/
public class WherePo {
private String relation;//关系 and/or...
private String file;//字段名
private String where;//条件
private String value;//值
public WherePo() {
}
public WherePo(String file, String where, String value) {
this.file = file;
this.where = where;
this.value = value;
}
public WherePo(String file, String where, String value,String relation) {
this.file = file;
this.where = where;
this.value = value;
this.relation = relation;
}//省略set/get方法
}
在DaoImpl中sqlSessionTemplateASS.insert("add", mybatisPo);就是调用dao-mapper.xml中id为add的方法,传入的参数为mybatisPo,其他的以此类推。接下来我们来看一下dao-mapper.xml如何实现通用。
3.dao-mapper.xml映射文件的实现
insert into ${tableName}
${item.file}
values
#{item.value}
;
insert into ${tableName}
${item.file}
values
#{item.value}
;
update ${tableName}
${item.file}=#{item.value},
delete from ${tableName}
${item.file} ${item.where}
CONCAT('%',#{item.value},'%')
AND ${item.file} ${item.where}
CONCAT('%',#{item.value},'%')
OR ${item.file} ${item.where}
CONCAT('%',#{item.value},'%')
${item.file} ${item.where} #{item.value}
AND ${item.file} ${item.where} #{item.value}
OR ${item.file} ${item.where} #{item.value}
${where.orderBy}
${where.limit}
;
用户输入的参数都用#{}传入到sql,而一些表名、列名等非用户输入的则用${}直接显示,用这种方法实现通用sql。
4.spring-application.xml配置
还需要在application.xml中做一些配置,然后在web.xml中加载此配置文件:
到这里所有核心代码就差不多了,接下来简单说一下在运用时的一些例子。
5.一些有涉及到的类
/**
* 实体类字段约束注解
* 标有此注解的字段对应数据库中的字段名强制约束为该注解中的name值
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldName {
String name() default PUBVALUE.FIELD_NAME_DEFAUL_VALUE;
}
/**
* 标识某个字段不录入数据库中
* 标识该注解的字段将没有增删改查的功能。
* @author Administrator
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TempField {
}
/**
* 标识一个实体类对应数据库表的表名
* 拥有该注解的PO类执行增删改差的时候将优先使用该注解内的值
* @author Administrator
*
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TableName {
String name();
}
这两个注解是标在实体类属性上的。
6.应用
在这里有一些比较复杂或者其他特殊要求的dao要自己在对于的Dao中实现,这个工具暂时也还没考虑到一些性能特殊要求的实现办法,欢迎一起探讨学习。