上一篇文章给大家介绍了基本的Dao封装和领域模型与SQL语句对应的方式。本节介绍一下如何使领域模型与SQL对应。
我们先理一下思路:
- SQL语句与领域模型对应时需要哪些信息
- JDBC的结果集ResultSet如何自动封装到对应的领域模型
- 运用反射解析出来的领域模型信息有个上下文,方便用的时候去取,防止反复解析领域模型
- 领域模型中有些字段是通过表连接获取的外表字段,但是insert语句的时候又不需要这些字段,仅仅是封装ResultSet的时候才需要,如何避免
- 假定我们的开发规范是根据领域模型字段的驼峰命名规则生成对应的下划线数据库表字段--这样可以在数据库中通用,如name生成NAME_、userName生成USER_NAME_
需要的领域模型信息如下:
package com.applet.sql.record;
import java.lang.reflect.Method;
/**
* 结果集返回表结构
* Created by Jackie Liu on 2017/8/20.
*/
public class TableColumn {
private String fieldName;
private String columnName;
private Class<?> javaType;
private Method fieldGetMethod;
private Method fieldSetMethod;
private boolean isTransient = false;
private boolean isPrimaryKey = false;
private ExtendType extendType;
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public Class<?> getJavaType() {
return javaType;
}
public void setJavaType(Class<?> javaType) {
this.javaType = javaType;
}
public Method getFieldGetMethod() {
return fieldGetMethod;
}
public void setFieldGetMethod(Method fieldGetMethod) {
this.fieldGetMethod = fieldGetMethod;
}
public Method getFieldSetMethod() {
return fieldSetMethod;
}
public void setFieldSetMethod(Method fieldSetMethod) {
this.fieldSetMethod = fieldSetMethod;
}
public boolean isTransient() {
return isTransient;
}
public void setTransient(boolean aTransient) {
isTransient = aTransient;
}
public boolean isPrimaryKey() {
return isPrimaryKey;
}
public void setPrimaryKey(boolean primaryKey) {
isPrimaryKey = primaryKey;
}
public ExtendType getExtendType() {
return extendType;
}
public void setExtendType(ExtendType extendType) {
this.extendType = extendType;
}
public String getPlaceholder() {
if (extendType != null) {
return extendType.getPlaceholder();
}
return "?";
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("TableColumn{");
sb.append("fieldName='").append(fieldName).append('\'');
sb.append(", columnName='").append(columnName).append('\'');
sb.append(", javaType=").append(javaType);
sb.append(", fieldGetMethod=").append(fieldGetMethod);
sb.append(", fieldSetMethod=").append(fieldSetMethod);
sb.append(", isTransient=").append(isTransient);
sb.append(", isPrimaryKey=").append(isPrimaryKey);
sb.append(", extendType=").append(extendType);
sb.append('}');
return sb.toString();
}
}
领域模型信息的解析如下:
package com.applet.sql.record;
import com.applet.sql.parse.BeanNameConverter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import javax.persistence.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by Jackie Liu on 2017/6/28.
*/
public class DomainModelAnalysis {
private Class<?> clazz;
//数据库名称
private String tableName;
//主键名称
private String primaryKey;
//默认的数据库字段拼接字符串,例如:_NAME, _NAME2, _NAME3
private String defaultColumnArrayStr;
//字段集合
private List<TableColumn> tableColumnList = new ArrayList<TableColumn>();
/**
* 获取实体类的信息
*/
public void analysisBean() {
Assert.notNull(clazz);
Entity entity = clazz.getAnnotation(Entity.class);
if (entity == null) {
return;
}
//获取数据库表名称
Table table = clazz.getAnnotation(Table.class);
Assert.notNull(table, String.format("[%s] @Table is null", clazz.getName()));
Assert.notNull(table.name(), String.format("[%s] the value of @Table's name is null", clazz.getName()));
tableName = table.name();
ReflectionUtils.clearCache();
//Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
Field[] fields = FieldUtils.getAllFields(clazz);
for (Field field : fields) {
if (field.getAnnotations().length <= 0) {
continue;
}
TableColumn tableColumn = new TableColumn();
tableColumn.setFieldName(field.getName());
tableColumn.setJavaType(field.getType());
String setMethodName = "set" + StringUtils.capitalize(field.getName());
Method setMethod = ReflectionUtils.findMethod(clazz, setMethodName, field.getType());
Assert.notNull(setMethod, String.format("[%s] : file name [%s] does not has set method. Expect method : %s", clazz.getName(), field.getName(), setMethodName));
tableColumn.setFieldSetMethod(setMethod);
String getMethodName = "get" + StringUtils.capitalize(field.getName());
Method getMethod = ReflectionUtils.findMethod(clazz, getMethodName);
Assert.notNull(getMethod, String.format("[%s] : file name [%s] does not has get method. Expect method : %s", clazz.getName(), field.getName(), getMethodName));
tableColumn.setFieldGetMethod(getMethod);
Id key = field.getAnnotation(Id.class);
Column column = field.getAnnotation(Column.class);
ColumnExtend columnExtend = field.getAnnotation(ColumnExtend.class);
Transient transientAn = field.getAnnotation(Transient.class);
TransientField transientField = field.getAnnotation(TransientField.class);
if (column != null) {
if (StringUtils.isBlank(column.name())) {
tableColumn.setColumnName(BeanNameConverter.camelToUnderline(field.getName()));
} else {
tableColumn.setColumnName(column.name().toUpperCase());
}
}
if (columnExtend != null) {
tableColumn.setExtendType(columnExtend.extendType());
}
if (transientField != null) {
if (StringUtils.isBlank(transientField.name())) {
tableColumn.setColumnName(BeanNameConverter.camelToUnderline(field.getName()));
} else {
tableColumn.setColumnName(transientField.name().toUpperCase());
}
tableColumn.setTransient(true);
}
if (transientAn != null) {
tableColumn.setColumnName(BeanNameConverter.camelToUnderline(field.getName()));
tableColumn.setTransient(true);
}
if (key != null) {
Assert.notNull(column, String.format("[%s] : file name [%s] has @Id, but does not has @Column", clazz.getName(), field.getName()));
primaryKey = tableColumn.getColumnName();
tableColumn.setPrimaryKey(true);
}
Assert.notNull(tableColumn.getColumnName(), String.format("[%s] : file name [%s] does not has column name (see @Column with name value or @TransientField with name value)", clazz.getName(), field.getName()));
tableColumnList.add(tableColumn);
}
defaultColumnArrayStr = joinColumn(", ");
}
/**
* 拼装数据库字段,split为分隔符
*
* @param split 分隔符
* @return 如:_NAME1, _NAME2, _NAME3
*/
public String joinColumn(String split) {
StringBuilder stringBuilder = new StringBuilder();
int index = 0;
for (int i = 0, size = tableColumnList.size(); i < size; i++) {
TableColumn tableColumn = tableColumnList.get(i);
if (tableColumn.isTransient()) {
continue;
}
if (index == 0) {
stringBuilder.append(tableColumn.getColumnName());
index++;
continue;
}
stringBuilder.append(split).append(tableColumn.getColumnName());
}
return stringBuilder.length() > 0 ? stringBuilder.toString() : null;
}
/**
* 拼装数据库字段,split为分隔符
*
* @param split 分隔符
* @return 如:{"_NAME1, _NAME2, _NAME3", "?, ?, ?"}
*/
public String[] joinColumnWithPlaceholder(String split) {
StringBuilder stringBuilder = new StringBuilder();
StringBuilder phBuilder = new StringBuilder();
int index = 0;
for (int i = 0, size = tableColumnList.size(); i < size; i++) {
TableColumn tableColumn = tableColumnList.get(i);
if (tableColumn.isTransient()) {
continue;
}
if (index == 0) {
stringBuilder.append(tableColumn.getColumnName());
phBuilder.append(tableColumn.getPlaceholder());
index++;
continue;
}
stringBuilder.append(split).append(tableColumn.getColumnName());
phBuilder.append(split).append(tableColumn.getPlaceholder());
}
return stringBuilder.length() > 0 ? new String[]{stringBuilder.toString(), phBuilder.toString()} : null;
}
public List<TableColumn> getTableColumnList() {
return tableColumnList;
}
public Class<?> getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
public String getDefaultColumnArrayStr() {
return defaultColumnArrayStr;
}
public String getTableName() {
return tableName;
}
public String getPrimaryKey() {
return primaryKey;
}
/**
* 获取tableColumn对象
*
* @param columnName 数据库列名
* @return
*/
public TableColumn getTableColumnByColumnName(String columnName) {
if (StringUtils.isEmpty(columnName)) {
return null;
}
for (TableColumn tableColumn : tableColumnList) {
if (tableColumn.getColumnName().equals(columnName)) {
return tableColumn;
}
}
return null;
}
/**
* 获取tableColumn对象
*
* @param fieldName java属性名
* @return
*/
public TableColumn getTableColumnByFieldName(String fieldName) {
if (StringUtils.isEmpty(fieldName)) {
return null;
}
for (TableColumn tableColumn : tableColumnList) {
if (tableColumn.getFieldName().equals(fieldName)) {
return tableColumn;
}
}
return null;
}
@Override
public String toString() {
if (StringUtils.isBlank(tableName)) {
return "[" + clazz.getName() + "] is not domain class.";
}
final StringBuilder sb = new StringBuilder("DomainModelAnalysis{");
sb.append("clazz=").append(clazz.getName());
sb.append(", tableName='").append(tableName).append('\'');
sb.append(", primaryKey='").append(primaryKey).append('\'');
sb.append(", defaultColumnArrayStr='").append(defaultColumnArrayStr).append('\'');
sb.append(", tableColumnList=").append(tableColumnList);
sb.append('}');
return sb.toString();
}
}
l领域模型缓存的上下文如下:
package com.applet.sql.record;
import com.applet.bean.PackageScanner;
import com.applet.sql.type.TypeHandlerRegistry;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* CommonModelContext class
*
* @author Jackie Liu
* @date 2017/10/17
*/
public class DomainModelContext implements InitializingBean {
protected static final Logger log = Logger.getLogger(DomainModelContext.class);
/**
* 扩展的领域模型完整类名
*/
private Set<String> extendClassSet;
/**
* 扫描包,读取包下所有的实体类并缓存符合条件的领域模型信息,多个包用英文逗号分隔
*/
private String scanPackages;
/**
* 领域模型解析集合
*/
private Map<Class<?>, DomainModelAnalysis> modelMap = new HashMap<Class<?>, DomainModelAnalysis>();
/**
* 数据库字段转换器
*/
private static final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
public DomainModelContext() {
}
public DomainModelAnalysis getDomainModelAnalysis(Class<?> clazz) {
DomainModelAnalysis domainModelAnalysis = modelMap.get(clazz);
if (domainModelAnalysis == null) {
domainModelAnalysis = registerBean(clazz);
}
return domainModelAnalysis;
}
public void addDomainModelAnalysis(DomainModelAnalysis domainModelAnalysis) {
modelMap.put(domainModelAnalysis.getClazz(), domainModelAnalysis);
}
public Set<String> getExtendClassSet() {
return extendClassSet;
}
public void setExtendClassSet(Set<String> extendClassSet) {
this.extendClassSet = extendClassSet;
}
public String getScanPackages() {
return scanPackages;
}
public void setScanPackages(String scanPackages) {
this.scanPackages = scanPackages;
}
public static TypeHandlerRegistry getTypeHandlerRegistry() {
return typeHandlerRegistry;
}
public DomainModelAnalysis registerBean(Class<?> clazz) {
synchronized (modelMap) {
DomainModelAnalysis domainModelAnalysis = new DomainModelAnalysis();
domainModelAnalysis.setClazz(clazz);
domainModelAnalysis.analysisBean();
if (log.isDebugEnabled()) {
log.debug(domainModelAnalysis);
}
modelMap.put(clazz, domainModelAnalysis);
return domainModelAnalysis;
}
}
@Override
public void afterPropertiesSet() throws Exception {
if (extendClassSet == null) {
extendClassSet = new HashSet<String>();
}
if (StringUtils.isNotBlank(scanPackages)) {
Set<String> list = PackageScanner.findPackageClass(scanPackages);
if (list.size() > 0) {
extendClassSet.addAll(list);
}
}
for (String className : extendClassSet) {
Class<?> clazz = Class.forName(className);
registerBean(clazz);
}
}
}
DomainModelContext支持通过配置实体bean和扫描包的方式解析领域模型,具体在spring.xml中配置方式如下:
<!-- 领域模型上下文 -->
<bean id="domainModelContext" class="com.applet.sql.record.DomainModelContext">
<property name="extendClassSet">
<set>
<value>com.jackiee.business.model.Report</value>
</set>
</property>
<property name="scanPackages" value="com.jackie"/>
</bean>
领域模型解析完成之后,就可以通过DomainModelContext获取实体信息了,如:
@Override
public <T> int insert(T t) {
DomainModelContext domainModelContext = SpringContextHelper.getBean(DomainModelContext.class);
DomainModelAnalysis domainModelAnalysis = domainModelContext.getDomainModelAnalysis(t.getClass());
List<TableColumn> tableColumnList = domainModelAnalysis.getTableColumnList();
List<Object> list = new ArrayList<Object>();
for (int i = 0, size = tableColumnList.size(); i < size; i++) {
TableColumn tableColumn = tableColumnList.get(i);
if (tableColumn.isTransient()) {
continue;
}
Object value = ReflectionUtils.invokeMethod(tableColumn.getFieldGetMethod(), t);
ExtendType extendType = tableColumn.getExtendType();
if (extendType != null && extendType.getCode() != ExtendType.DEFAULT.getCode()) {
value = value.toString();
}
list.add(value);
}
String[] array = domainModelAnalysis.joinColumnWithPlaceholder(", ");
String sql = String.format("INSERT INTO %s (%s) VALUES (%s)", domainModelAnalysis.getTableName(), array[0], array[1]);
return getJdbcTemplate().update(sql, list.toArray(new Object[0]));
}
有了这些有用的信息之后,下一篇将为大家介绍如何自动封装ResultSet结果集。