通常我们在开发Java企业级应用的时候使用的技术大部分是Spring、Hibernate、mybatis、Struts2等。尤其是Spring,相信这个庞大而优雅的技术体系你已经离不开了,在我们项目代码中基本是骨干力量的存在。
而我们使用的ORM框架大多数也是Hibernate、mybatis或者Spring提供的jdbc简单封装的JdbcTemplate。如果我们的项目组开发人员对所选型的ORM框架很熟悉并能熟练使用是再好不过了,但是大部分情况下项目组成员的技术水平是参差不齐的,有时会踩到所选型ORM框架的坑。比如Hibernate很容易引起性能问题,mybatis在重构时很麻烦等等增加了项目的交付风险。
本文希望能在Dao层的开发上做的简单,健壮,提高开发效率:
1 对JdbcTemplate做一层简单的封装,能够对单表的CRUD做到无需写SQL语句
2 在重构SQL代码时对SQL层做少量修改或者不修改
我们开始吧!
我们先考虑一下:如果做到CRUD的不需要写SQL,这也意味着SQL是要自动生成的。
我们先看SQL语句的组成:
1 添加语句: insert into tableName (id, name, ...) values (1, 'name', ...);
2 修改语句:update tableName set name = 'name', age = 'age' where id = 'id';
3 删除语句:delete from tableName where id = 'id';
4 查询语句:select id, name from tableName where age > 18;
一般我们做JAVA企业级开发都会有领域模型的概念,这个领域模型会对应一个数据库表并包括这个数据库表的所有字段。那么这时我们可以利用领域模型的字段生成对应的SQL语句。这里我们先借用JPA的注解完成领域模型属性与数据库表的映射,熟悉hibernate的朋友一定不会陌生,当然你也可以自己定义注解。
如下领域模型:
package com.applet.model;import com.fasterxml.jackson.databind.annotation.JsonSerialize;import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;import com.applet.base.BaseModel;import com.applet.enumeration.YesNoEnum;import com.applet.utils.DateUtils;import javax.persistence.*;import java.io.Serializable;import java.util.Date;@Entity@Table(name = "TS_ROLE")public class Role implements Serializable { /** * * Field serialVersionUID: 序列号 *
*/ private static final long serialVersionUID = 1L; //主键 @Id @Column @JsonSerialize(using = ToStringSerializer.class) protected Long id; // 名称 @Column private String name; // 状态(1启用,2停用) @Column private Integer state; // 创建人id @Column @JsonSerialize(using = ToStringSerializer.class) private Long createId; // 创建日期 @Temporal(TemporalType.TIMESTAMP) @Column private Date createTime; // 是否系统角色(1是,2否) @Column private Integer isSys; // 描述 @Column private String remark; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getStateStr() { return YesNoEnum.valueOfValidateLabel(state); } public String getIsSysStr() { return YesNoEnum.valueOf(isSys); } public String getCreateTimeStr() { if (createTime != null) { return DateUtils.dateTimeToString(createTime); } return null; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getState() { return state; } public void setState(Integer state) { this.state = state; } public Long getCreateId() { return createId; } public void setCreateId(Long createId) { this.createId = createId; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public Integer getIsSys() { return isSys; } public void setIsSys(Integer isSys) { this.isSys = isSys; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; }}复制代码
我们的基础的dao接口如下:
package com.applet.base;import java.io.Serializable;import java.util.List;public interface BaseDao { /** * 判断某一字段是否重复 * @param id 实体id * @param filedValue 字段值 * @param fieldName 字段名称 * @return */ //public boolean isDuplicateField(PK id, Object filedValue, String fieldName); /** * Description: 添加实体
* @param t 实体对象 */ public int insert(T t); /** * Description: 批量添加实体
* @param list 实体对象列表 */ public int batchInsert(final List list); /** * Description: 更新实体,字段值为null的不更新
* @param t 实体对象 */ public int update(T t); /** * Description: 更新实体
* @param t 实体对象 */ public int updateForce(T t); /** * Description: 根据id删除实体
* @param id 实体id值 */ public int delete(PK id); /** * Description: 批量删除实体
* @param ids 实体id值数组 */ public int delete(PK[] ids); /** * Description: 按条件查询实体列表
* @param wb QueryCondition对象 * @return 实体列表 */ //public List query(QueryCondition wb); /** * Description: 按条件查询实体数量
* @param wb QueryCondition对象 * @return 实体数量 */ //public int count(QueryCondition wb); /** * Description: 根据id查询实体
* @param id 实体id值 * @return 实体对象 */ public T load(PK id); /** * Description: 按条件删除实体
* @param wb QueryCondition对象 */ //public int deleteByCondition(QueryCondition wb); /** * Description: 分页查询
* @param wb QueryCondition对象 * @return */ //public Page queryPage(QueryCondition wb);}复制代码
我们基础的dao接口实现如下:
package com.applet.base;import com.applet.sql.builder.SelectBuilder;import com.applet.sql.builder.WhereBuilder;import com.applet.sql.mapper.DefaultRowMapper;import com.applet.sql.page.PageSql;import com.applet.sql.record.DomainModelAnalysis;import com.applet.sql.record.DomainModelContext;import com.applet.sql.record.ExtendType;import com.applet.sql.record.TableColumn;import com.applet.sql.type.JdbcType;import com.applet.sql.type.TypeHandler;import com.applet.sql.type.TypeHandlerRegistry;import com.applet.utils.KeyUtils;import com.applet.utils.Page;import com.applet.utils.SpringContextHelper;import org.apache.commons.lang3.StringUtils;import org.apache.log4j.Logger;import org.springframework.beans.factory.InitializingBean;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.dao.support.DataAccessUtils;import org.springframework.jdbc.core.BatchPreparedStatementSetter;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.util.ReflectionUtils;import java.io.Serializable;import java.lang.reflect.Method;import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;import java.sql.PreparedStatement;import java.sql.SQLException;import java.util.ArrayList;import java.util.List;import java.util.Map;public class BaseDaoImpl implements BaseDao , InitializingBean { protected static final Logger log = Logger.getLogger(BaseDaoImpl.class); @Autowired protected JdbcTemplate jdbcTemplate; @Autowired protected PageSql pageSql; protected Class modelClass; protected DomainModelAnalysis domainModelAnalysis; public BaseDaoImpl() { } @SuppressWarnings("unchecked") protected Class autoGetDomainClass() { if (modelClass == null) { Type type = this.getClass().getGenericSuperclass(); if (type instanceof ParameterizedType) { modelClass = (Class ) ((ParameterizedType) type).getActualTypeArguments()[0]; } else { throw new RuntimeException("SubClass must give the ActualTypeArguments"); } } return modelClass; } /** * 获取添加实体的SQL语句 * * @return */ protected String getInsertSql() { String[] array = domainModelAnalysis.joinColumnWithPlaceholder(", "); String sql = String.format("INSERT INTO %s (%s) VALUES (%s)", domainModelAnalysis.getTableName(), array[0], array[1]); return sql; } /** * 将完整的SQL转换为统计SQL * 如:select a, b, c, t.d from table t * 转换后为:select count(1) from table t * * @param sql * @return */ protected String toCountSql(String sql) { if (StringUtils.isEmpty(sql)) { return null; } return sql.replaceFirst("(?<=(?i)SELECT).*?(?=(?i)FROM)", " COUNT\\(1\\) ").replaceAll("(?=(?i)order).*", ""); } /** * 添加实体 * * @param t 实体对象 * @return */ @Override public int insert(T t) { List tableColumnList = domainModelAnalysis.getTableColumnList(); List
这里最关键的是如何解析领域模型的属性信息,我们可以在运行时通过反射把领域模型的属性信息解析出来并全局缓存起来。这步操作是在BaseDaoImpl.java的如下代码完成的---如果你不熟悉InitializingBean接口,可以搜索一下它的意义:
@Overridepublic void afterPropertiesSet() throws Exception { DomainModelContext domainModelContext = SpringContextHelper.getBean(DomainModelContext.class); domainModelAnalysis = domainModelContext.registerBean(autoGetDomainClass());}复制代码
下一节为大家介绍如何使用Java反射把领域模型的属性信息解析出来并全局缓存起来。