JAVA开发之简化Dao层、提高开发效率(二)

上一篇文章给大家介绍了基本的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结果集。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,513评论 18 399
  • 一. Java基础部分.................................................
    wy_sure阅读 3,777评论 0 11
  • 通常我们在开发Java企业级应用的时候使用的技术大部分是Spring、Hibernate、mybatis、Stru...
    水云斋阅读 869评论 0 1
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,400评论 0 4
  • 最近几天又开始拿着手机开始玩游戏,而且玩的还是比较疯狂的,前期自我安慰,说的好听是,最近也没事情做,天天学习多累啊...
    飘渺_d65f阅读 608评论 7 11