JAVA開發之簡化Dao層、提高開發效率(二)

草原上的格林發表於2019-03-01

上一篇文章給大家介紹了基本的Dao封裝和領域模型與SQL語句對應的方式。本節介紹一下如何使領域模型與SQL對應。
我們先理一下思路:

  1. SQL語句與領域模型對應時需要哪些資訊
  2. JDBC的結果集ResultSet如何自動封裝到對應的領域模型
  3. 運用反射解析出來的領域模型資訊有個上下文,方便用的時候去取,防止反覆解析領域模型
  4. 領域模型中有些欄位是通過表連線獲取的外表欄位,但是insert語句的時候又不需要這些欄位,僅僅是封裝ResultSet的時候才需要,如何避免
  5. 假定我們的開發規範是根據領域模型欄位的駝峰命名規則生成對應的下劃線資料庫表欄位–這樣可以在資料庫中通用,如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結果集。

相關文章