Упростите уровень Dao и повысьте эффективность разработки при разработке JAVA.

Java SQL

Обычно большинство технологий, которые мы используем при разработке корпоративных Java-приложений, — это Spring, Hibernate, mybatis, Struts2 и т. д. Особенно Spring, я считаю, что вы не можете жить без этой огромной и элегантной технической системы, которая является основой кода нашего проекта.

Большинство фреймворков ORM, которые мы используем, также представляют собой JdbcTemplate, просто инкапсулированные jdbc, предоставляемым Hibernate, mybatis или Spring. Если разработчики нашей проектной группы знакомы с выбранным ORM-фреймворком и умеют им пользоваться, это было бы здорово, но в большинстве случаев технический уровень членов проектной команды неодинаков, и иногда они наступают на выбранный тип ORM-фреймворка. Яма фреймворка ORM. Например, Hibernate может легко вызвать проблемы с производительностью, а mybatis создает проблемы при рефакторинге и т. д., что увеличивает риск реализации проекта.

В этой статье мы надеемся сделать разработку слоя Dao простой, надежной и повысить эффективность разработки:

1 Сделайте простую инкапсуляцию JdbcTemplate, которая может выполнять CRUD одной таблицы без написания операторов SQL.

2 Вносить небольшие изменения или вообще не вносить изменения в слой SQL при рефакторинге кода SQL.

Давайте начнем!

Давайте сначала подумаем: если вам не нужно писать SQL для достижения CRUD, это также означает, что SQL генерируется автоматически.

Давайте сначала посмотрим на состав оператора SQL:

1 Добавьте оператор: вставьте в tableName (id, name, ...) значения (1, 'name', ...);

2 Измените оператор: update tableName set name = 'name', age = 'age', где id = 'id';

3 Оператор удаления: удалить из tableName, где id = 'id';

4 Оператор запроса: выберите идентификатор, имя из tableName, где возраст > 18;

Как правило, когда мы занимаемся разработкой JAVA на уровне предприятия, у нас будет концепция модели предметной области, Эта модель предметной области будет соответствовать таблице базы данных и включать все поля этой таблицы базы данных. Затем мы можем использовать поля модели предметной области для генерации соответствующих операторов SQL. Здесь мы сначала используем аннотации JPA для завершения сопоставления между атрибутами модели домена и таблицами базы данных.Друзья, знакомые с спящим режимом, не будут незнакомы.Конечно, вы также можете определить аннотации самостоятельно.

Следующая модель предметной области:

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 {

    /**
     * <p>
     * Field serialVersionUID: 序列号
     * </p>
     */
    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;
    }
}

Наш основной интерфейс дао выглядит следующим образом:

package com.applet.base;

import java.io.Serializable;
import java.util.List;

public interface BaseDao<T extends BaseModel, PK extends Serializable>{
    
    /**
     * 判断某一字段是否重复
     * @param id 实体id
     * @param filedValue 字段值
     * @param fieldName 字段名称
     * @return
     */
    //public boolean isDuplicateField(PK id, Object filedValue, String fieldName);
    
    /**
     * <p>Description: 添加实体</p>
     * @param t 实体对象
     */
    public int insert(T t);

    /**
     * <p>Description: 批量添加实体</p>
     * @param list 实体对象列表
     */
    public int batchInsert(final List<T> list);

    /**
     * <p>Description: 更新实体,字段值为null的不更新</p>
     * @param t 实体对象
     */
    public int update(T t);

    /**
     * <p>Description: 更新实体</p>
     * @param t 实体对象
     */
    public int updateForce(T t);

    /**
     * <p>Description: 根据id删除实体</p>
     * @param id 实体id值
     */
    public int delete(PK id);

    /**
     * <p>Description: 批量删除实体</p>
     * @param ids 实体id值数组
     */
    public int delete(PK[] ids);

    /**
     * <p>Description: 按条件查询实体列表</p>
     * @param wb QueryCondition对象
     * @return 实体列表
     */
    //public List<T> query(QueryCondition wb);

    /**
     * <p>Description: 按条件查询实体数量</p>
     * @param wb QueryCondition对象
     * @return 实体数量
     */
    //public int count(QueryCondition wb);

    /**
     * <p>Description: 根据id查询实体</p>
     * @param id 实体id值
     * @return 实体对象
     */
    public T load(PK id);

    /**
     * <p>Description: 按条件删除实体</p>
     * @param wb QueryCondition对象
     */
    //public int deleteByCondition(QueryCondition wb);

    /**
     * <p>Description: 分页查询</p>
     * @param wb QueryCondition对象
     * @return
     */
    //public Page<T> 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<T extends BaseModel, PK extends Serializable> implements BaseDao<T, PK>, InitializingBean {

    protected static final Logger log = Logger.getLogger(BaseDaoImpl.class);

    @Autowired
    protected JdbcTemplate jdbcTemplate;
    @Autowired
    protected PageSql pageSql;

    protected Class<T> modelClass;
    protected DomainModelAnalysis domainModelAnalysis;

    public BaseDaoImpl() {
    }

    @SuppressWarnings("unchecked")
    protected Class<T> autoGetDomainClass() {
        if (modelClass == null) {
            Type type = this.getClass().getGenericSuperclass();
            if (type instanceof ParameterizedType) {
                modelClass = (Class<T>) ((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<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);
        }
        return jdbcTemplate.update(getInsertSql(), list.toArray(new Object[0]));
    }

    /**
     * 批量添加实体
     *
     * @param list 实体对象列表
     * @return
     */
    @Override
    public int batchInsert(final List<T> list) {
        final List<TableColumn> tableColumnList = domainModelAnalysis.getTableColumnList();
        final TypeHandlerRegistry typeHandlerRegistry = DomainModelContext.getTypeHandlerRegistry();
        return jdbcTemplate.batchUpdate(getInsertSql(), new BatchPreparedStatementSetter() {
            @SuppressWarnings({"rawtypes", "unchecked"})
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                T t = list.get(i);
                int index = 1;
                for (int k = 0, size = tableColumnList.size(); k < size; k++) {
                    TableColumn tableColumn = tableColumnList.get(k);
                    if (tableColumn.isTransient()) {
                        continue;
                    }
                    Object value = ReflectionUtils.invokeMethod(tableColumn.getFieldGetMethod(), t);
                    TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(tableColumn.getJavaType());
                    typeHandler.setParameter(ps, index, value, JdbcType.NULL);
                    index++;
                }
            }

            @Override
            public int getBatchSize() {
                return list.size();
            }
        }).length;
    }

    /**
     * 更新实体中的非空(不含null, "")字段
     *
     * @param t 实体对象
     * @return
     */
    @Override
    public int update(T t) {
        WhereBuilder wb = new WhereBuilder();
        wb.andEquals(domainModelAnalysis.getPrimaryKey(), t.getId());
        return updateByCondition(t, wb);
    }

    protected int updateByCondition(T t, WhereBuilder whereBuilder) {
        StringBuilder sqlBuilder = new StringBuilder(String.format("UPDATE %s SET ", domainModelAnalysis.getTableName()));
        List<TableColumn> tableColumnList = domainModelAnalysis.getTableColumnList();

        Method primaryKeyMethod = null;
        List<Object> values = new ArrayList<Object>();
        for (int i = 0, size = tableColumnList.size(); i < size; i++) {
            final TableColumn tableColumn = tableColumnList.get(i);
            if (tableColumn.isTransient()) {
                continue;
            }
            if (tableColumn.isPrimaryKey()) {
                primaryKeyMethod = tableColumn.getFieldGetMethod();
                continue;
            }
            Object value = ReflectionUtils.invokeMethod(tableColumn.getFieldGetMethod(), t);
            ExtendType extendType = tableColumn.getExtendType();
            if (extendType != null && extendType.getCode() != ExtendType.DEFAULT.getCode()) {
                value = value.toString();
            }
            if (value == null || (value instanceof String && StringUtils.isEmpty((String) value))) {
                continue;
            }

            values.add(value);
            sqlBuilder.append(tableColumn.getColumnName())
                    .append(" = ")
                    .append(tableColumn.getPlaceholder())
                    .append(", ");
        }

        if (values.size() > 0) {
            int length = sqlBuilder.length();
            sqlBuilder.delete(length - 2, length);
            String sql = sqlBuilder.toString();
            sql = whereBuilder.getSql(sql);
            values.addAll(whereBuilder.getParameterList());
            return jdbcTemplate.update(sql, values.toArray(new Object[0]));
        }
        return 0;
    }

    /**
     * 更新实体所有字段
     *
     * @param t 实体对象
     * @return
     */
    @Override
    public int updateForce(T t) {
        StringBuilder sqlBuilder = new StringBuilder(String.format("UPDATE %s SET ", domainModelAnalysis.getTableName()));
        List<TableColumn> tableColumnList = domainModelAnalysis.getTableColumnList();

        Method primaryKeyMethod = null;
        List<Object> values = new ArrayList<Object>();
        for (int i = 0, size = tableColumnList.size(); i < size; i++) {
            final TableColumn tableColumn = tableColumnList.get(i);
            if (tableColumn.isTransient()) {
                continue;
            }
            if (tableColumn.isPrimaryKey()) {
                primaryKeyMethod = tableColumn.getFieldGetMethod();
                continue;
            }
            Object value = ReflectionUtils.invokeMethod(tableColumn.getFieldGetMethod(), t);
            values.add(value);
            sqlBuilder.append(tableColumn.getColumnName())
                    .append(" = ")
                    .append(tableColumn.getPlaceholder())
                    .append(", ");
        }

        int length = sqlBuilder.length();
        sqlBuilder.delete(length - 2, length);
        sqlBuilder.append(" WHERE ").append(domainModelAnalysis.getPrimaryKey()).append(" = ?");
        values.add(ReflectionUtils.invokeMethod(primaryKeyMethod, t));
        return jdbcTemplate.update(sqlBuilder.toString(), values.toArray(new Object[0]));
    }

    /**
     * 根据主键删除实体
     *
     * @param id 实体主键值
     * @return
     */
    @Override
    public int delete(PK id) {
        String sql = String.format("DELETE FROM %s WHERE %s = ?", domainModelAnalysis.getTableName(), domainModelAnalysis.getPrimaryKey());
        return jdbcTemplate.update(sql, new Object[]{id});
    }

    /**
     * 根据主键删除实体
     *
     * @param ids 实体主键值数组
     * @return
     */
    @Override
    public int delete(final PK[] ids) {
        String sql = String.format("DELETE FROM %s WHERE %s = ?", domainModelAnalysis.getTableName(), domainModelAnalysis.getPrimaryKey());
        int[] batchUpdate = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {

            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setObject(1, ids[i]);
            }

            @Override
            public int getBatchSize() {
                return ids.length;
            }
        });
        return batchUpdate.length;
    }

    /**
     * 查询实体列表
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected List<T> query(WhereBuilder wb) {
        String sql = String.format("SELECT %s FROM %s", domainModelAnalysis.getDefaultColumnArrayStr(), domainModelAnalysis.getTableName());
        Object args[] = null;
        if (wb != null) {
            sql = wb.getSql(sql);
            args = wb.getParameters();
        }
        return jdbcTemplate.query(sql, args, new DefaultRowMapper<T>(domainModelAnalysis));
    }

    /**
     * 查询实体列表
     *
     * @param sql select语句
     * @param wb  where语句拼接实例
     * @return
     */
    protected List<T> query(String sql, WhereBuilder wb) {
        Object args[] = null;
        if (wb != null) {
            sql = wb.getSql(sql);
            args = wb.getParameters();
        }
        return jdbcTemplate.query(sql, args, new DefaultRowMapper<T>(domainModelAnalysis));
    }

    /**
     * 限制返回查询记录数量
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected List<T> queryLimit(WhereBuilder wb) {
        String sql = String.format("SELECT %s FROM %s", domainModelAnalysis.getDefaultColumnArrayStr(), domainModelAnalysis.getTableName());
        return queryLimit(sql, wb);
    }

    /**
     * 限制返回查询记录数量
     *
     * @param querySql select语句
     * @param wb       where语句拼接实例
     * @return
     */
    protected List<T> queryLimit(String querySql, WhereBuilder wb) {
        Object args[] = null;
        int pageNum = 0, pageSize = 0;
        if (wb != null) {
            pageNum = wb.getPageNum();
            pageSize = wb.getPageSize();
            querySql = wb.getSql(querySql);
            args = wb.getParameters();
        }
        String qsql = pageSql.getSql(querySql, pageNum, pageSize);
        List<T> list = jdbcTemplate.query(qsql, args, new DefaultRowMapper<T>(domainModelAnalysis));
        return list;
    }

    /**
     * 按条件统计实体数量
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected int count(WhereBuilder wb) {
        String sql = String.format("SELECT COUNT(1) FROM %s", domainModelAnalysis.getTableName());
        Object args[] = null;
        if (wb != null) {
            sql = wb.getSql(sql);
            args = wb.getParameters();
        }
        return jdbcTemplate.queryForObject(sql, args, Integer.class);
    }

    /**
     * 根据主键查询实体
     *
     * @param id 实体主键值
     * @return
     */
    @Override
    public T load(PK id) {
        String sql = String.format("SELECT %s FROM %s WHERE %s = ?", domainModelAnalysis.getDefaultColumnArrayStr(), domainModelAnalysis.getTableName(), domainModelAnalysis.getPrimaryKey());
        List<T> list = jdbcTemplate.query(sql, new Object[]{id}, new DefaultRowMapper<T>(domainModelAnalysis));
        return DataAccessUtils.singleResult(list);
    }

    /**
     * 按条件删除实体
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected int deleteByCondition(WhereBuilder wb) {
        String sql = String.format("DELETE FROM %s", domainModelAnalysis.getTableName());
        Object args[] = null;
        if (wb != null) {
            sql = wb.getSql(sql);
            args = wb.getParameters();
        }
        return jdbcTemplate.update(sql, args);
    }

    /**
     * 分页查询
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected Page<T> queryPage(WhereBuilder wb) {
        String sql = String.format("SELECT %s FROM %s ", domainModelAnalysis.getDefaultColumnArrayStr(), domainModelAnalysis.getTableName());
        return queryPage(sql, wb);
    }

    /**
     * 分页查询
     *
     * @param sql select语句
     * @param wb  where语句拼接实例
     * @return
     */
    protected Page<T> queryPage(String sql, WhereBuilder wb) {
        String countSql = toCountSql(sql);
        return queryPage(sql, countSql, wb);
    }

    /**
     * 分页查询
     *
     * @param querySql select语句
     * @param countSql count语句
     * @param wb       where语句拼接实例
     * @return
     */
    protected Page<T> queryPage(String querySql, String countSql, WhereBuilder wb) {
        Object args[] = null;
        int pageNum = 0, pageSize = 0;
        if (wb != null) {
            querySql = wb.getSql(querySql);
            args = wb.getParameters();
            pageSize = wb.getPageSize();
            pageNum = wb.getPageNum();
        }
        String qsql = pageSql.getSql(querySql, pageNum, pageSize);
        List<T> list = jdbcTemplate.query(qsql, args, new DefaultRowMapper<T>(domainModelAnalysis));

        Page<T> page = new Page<T>();
        page.setData(list);

        if (StringUtils.isNotEmpty(countSql)) {
            String csql = wb.getCountSql(countSql);
            long count = jdbcTemplate.queryForObject(csql, args, Long.class);
            page.setTotal(count);
        }
        return page;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        DomainModelContext domainModelContext = SpringContextHelper.getBean(DomainModelContext.class);
        domainModelAnalysis = domainModelContext.registerBean(autoGetDomainClass());
    }
}

Наиболее важным здесь является то, как анализировать информацию об атрибутах модели предметной области Мы можем анализировать информацию об атрибутах модели предметной области посредством отражения во время выполнения и кэшировать ее глобально. Этот шаг выполняется в следующем коде в BaseDaoImpl.java — если вы не знакомы с интерфейсом InitializingBean, вы можете найти его значение:

@Override
public void afterPropertiesSet() throws Exception {
        DomainModelContext domainModelContext = SpringContextHelper.getBean(DomainModelContext.class);
        domainModelAnalysis = domainModelContext.registerBean(autoGetDomainClass());
}

В следующем разделе будет показано, как использовать отражение Java для анализа информации об атрибутах модели предметной области и ее глобального кэширования.