package org.springframework.data.mybatis.repository.support; import static javax.persistence.GenerationType.AUTO; import static javax.persistence.GenerationType.IDENTITY; import static javax.persistence.GenerationType.SEQUENCE; import static org.apache.ibatis.mapping.SqlCommandType.DELETE; import static org.apache.ibatis.mapping.SqlCommandType.INSERT; import static org.apache.ibatis.mapping.SqlCommandType.SELECT; import static org.apache.ibatis.mapping.SqlCommandType.UPDATE; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.persistence.EmbeddedId; import javax.persistence.GeneratedValue; import javax.persistence.SequenceGenerator; import javax.persistence.SequenceGenerators; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mybatis.annotation.Condition; import org.springframework.data.mybatis.annotation.Conditions; import org.springframework.data.mybatis.annotation.CreatedBy; import org.springframework.data.mybatis.annotation.CreatedDate; import org.springframework.data.mybatis.annotation.Snowflake; import org.springframework.data.mybatis.id.SnowflakeKeyGenerator; import org.springframework.data.mybatis.mapping.MybatisPersistentEntityImpl; import org.springframework.data.mybatis.mapping.MybatisPersistentProperty; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.query.parser.Part.IgnoreCaseType; import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.util.StringUtils; import org.apache.ibatis.executor.keygen.KeyGenerator; import org.apache.ibatis.executor.keygen.NoKeyGenerator; import org.apache.ibatis.executor.keygen.SelectKeyGenerator; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ResultFlag; import org.apache.ibatis.mapping.ResultMapping; import org.apache.ibatis.session.Configuration; public class MybatisBasicMapperBuilder extends MybatisMapperBuildAssistant { public static final String DEFAULT_SEQUENCE_NAME = "seq_spring_data_mybatis"; public MybatisBasicMapperBuilder(Configuration configuration, RepositoryInformation repositoryInformation, PersistentEntity<?, ?> persistentEntity) { super(configuration, persistentEntity, repositoryInformation.getRepositoryInterface().getName()); } @Override protected void doBuild() { addResultMap(); addInsertStatement(); addUpdateStatement(true, true); addUpdateStatement(false, true); addUpdateStatement(true, false); addUpdateStatement(false, false); addGetByIdStatement(); addCountStatement(); addCountAllStatement(); addDeleteByIdStatement(); addDeleteAllStatement(); addFindStatement(true); addFindStatement(false); } private void addResultMap() { List<ResultMapping> resultMappings = new ArrayList<>(); entity.doWithProperties((PropertyHandler<MybatisPersistentProperty>) p -> { if (p.isAnnotationPresent(EmbeddedId.class) || p.isEmbeddable()) { ((MybatisPersistentEntityImpl) entity) .getRequiredPersistentEntity(p.getActualType()).doWithProperties( (PropertyHandler<MybatisPersistentProperty>) ep -> { resultMappings.add( assistant.buildResultMapping(ep.getType(), String.format("%s.%s", p.getName(), ep.getName()), ep.getColumnName(), ep.getType(), ep.getJdbcType(), null, null, null, null, ep.getSpecifiedTypeHandler(), p.isIdProperty() ? Collections.singletonList( ResultFlag.ID) : null)); }); return; } resultMappings.add(assistant.buildResultMapping(p.getType(), p.getName(), p.getColumnName(), p.getType(), p.getJdbcType(), null, null, null, null, p.getSpecifiedTypeHandler(), p.isIdProperty() ? Collections.singletonList(ResultFlag.ID) : null)); }); addResultMap(RESULT_MAP, entity.getType(), resultMappings); } private void addInsertStatement() { StringBuilder builder = new StringBuilder(); KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE; String keyProperty = null, keyColumn = null; builder.append("insert into ").append(entity.getTableName()).append(" ("); MybatisPersistentProperty idProperty = entity.getIdProperty(); if (null != idProperty) { keyProperty = idProperty.getName(); keyColumn = idProperty.getColumnName(); if (idProperty.isAnnotationPresent(Snowflake.class)) { keyGenerator = new SnowflakeKeyGenerator(keyProperty, ((MybatisPersistentEntityImpl) this.entity).getMappingContext() .getSnowflake()); configuration.addKeyGenerator(assistant.applyCurrentNamespace( "__insert" + SnowflakeKeyGenerator.SELECT_KEY_SUFFIX, false), keyGenerator); } else if (idProperty.isAnnotationPresent(GeneratedValue.class)) { GeneratedValue gv = idProperty .getRequiredAnnotation(GeneratedValue.class); String gid = "__insert" + SelectKeyGenerator.SELECT_KEY_SUFFIX; String[] sqls; boolean executeBefore; if (gv.strategy() == IDENTITY || (gv.strategy() == AUTO && "identity" .equals(dialect.getNativeIdentifierGeneratorStrategy()))) { // identity sqls = new String[] { dialect.getIdentityColumnSupport().getIdentitySelectString( entity.getTableName(), idProperty.getColumnName(), idProperty.getJdbcType().TYPE_CODE) }; executeBefore = false; } else if (gv.strategy() == SEQUENCE || (gv.strategy() == AUTO && "sequence" .equals(dialect.getNativeIdentifierGeneratorStrategy()))) { // sequence String sequenceName = DEFAULT_SEQUENCE_NAME; if (StringUtils.hasText(gv.generator())) { // search sequence generator Map<String, String> sequenceGenerators = new HashMap<>(); if (entity.isAnnotationPresent(SequenceGenerators.class)) { sequenceGenerators.putAll(Stream .of(entity.getRequiredAnnotation( SequenceGenerators.class).value()) .filter(sg -> StringUtils.hasText(sg.sequenceName())) .collect(Collectors.toMap(sg -> sg.name(), sg -> sg.sequenceName()))); } if (entity.isAnnotationPresent(SequenceGenerator.class)) { SequenceGenerator sg = entity .getRequiredAnnotation(SequenceGenerator.class); if (StringUtils.hasText(sg.sequenceName())) { sequenceGenerators.put(sg.name(), sg.sequenceName()); } } if (idProperty.isAnnotationPresent(SequenceGenerators.class)) { sequenceGenerators.putAll(Stream .of(idProperty.getRequiredAnnotation( SequenceGenerators.class).value()) .filter(sg -> StringUtils.hasText(sg.sequenceName())) .collect(Collectors.toMap(sg -> sg.name(), sg -> sg.sequenceName()))); } if (idProperty.isAnnotationPresent(SequenceGenerator.class)) { SequenceGenerator sg = idProperty .getRequiredAnnotation(SequenceGenerator.class); if (StringUtils.hasText(sg.sequenceName())) { sequenceGenerators.put(sg.name(), sg.sequenceName()); } } String sn = sequenceGenerators.get(gv.generator()); if (StringUtils.hasText(sn)) { sequenceName = sn; } } sqls = new String[] { dialect.getSequenceNextValString(sequenceName) }; executeBefore = true; } else { throw new UnsupportedOperationException( "unsupported generated value id strategy: " + gv.strategy()); } addMappedStatement(gid, sqls, SELECT, entity.getType(), null, idProperty.getActualType(), NoKeyGenerator.INSTANCE, idProperty.getName(), idProperty.getColumnName()); gid = assistant.applyCurrentNamespace(gid, false); MappedStatement keyStatement = configuration.getMappedStatement(gid, false); keyGenerator = new SelectKeyGenerator(keyStatement, executeBefore); configuration.addKeyGenerator(gid, keyGenerator); } } List<MybatisPersistentProperty> columns = findNormalColumns(); builder.append(columns.stream().map(p -> { if (p.isAnnotationPresent(EmbeddedId.class) || p.isEmbeddable()) { return findNormalColumns(((MybatisPersistentEntityImpl) entity) .getRequiredPersistentEntity(p.getActualType())).stream() .map(ep -> ep.getColumnName()) .collect(Collectors.joining(",")); } return p.getColumnName(); }).collect(Collectors.joining(","))); builder.append(") values ("); builder.append(columns.stream().map(p -> { if (p.isAnnotationPresent(EmbeddedId.class) || p.isEmbeddable()) { return findNormalColumns(((MybatisPersistentEntityImpl) entity) .getRequiredPersistentEntity(p.getActualType())) .stream() .map(ep -> null != ep.getSpecifiedTypeHandler() ? String.format( "#{%s.%s,jdbcType=%s,typeHandler=%s}", p.getName(), ep.getName(), ep.getJdbcType().name(), ep.getSpecifiedTypeHandler().getName()) : String.format("#{%s.%s,jdbcType=%s}", p.getName(), ep.getName(), ep.getJdbcType().name())) .collect(Collectors.joining(",")); } return null != p.getSpecifiedTypeHandler() ? String.format("#{%s,jdbcType=%s,typeHandler=%s}", p.getName(), p.getJdbcType().name(), p.getSpecifiedTypeHandler().getName()) : String.format("#{%s,jdbcType=%s}", p.getName(), p.getJdbcType().name()); } ).collect(Collectors.joining(","))); builder.append(")"); addMappedStatement("__insert", new String[] { builder.toString() }, INSERT, entity.getType(), null, null, keyGenerator, keyProperty, keyColumn); } private void addUpdateStatement(boolean ignoreNull, boolean byId) { if (!entity.hasIdProperty()) { return; } StringBuilder builder = new StringBuilder(); builder.append("update ").append(entity.getTableName()).append(" <set> "); builder.append( findNormalColumns().stream().filter(p -> !(p .isAnnotationPresent(CreatedDate.class) || p.isAnnotationPresent( org.springframework.data.annotation.CreatedDate.class) || p.isAnnotationPresent(CreatedBy.class) || p.isAnnotationPresent( org.springframework.data.annotation.CreatedBy.class))) .map(p -> { if (p.isAnnotationPresent(EmbeddedId.class) || p.isEmbeddable()) { return findNormalColumns( ((MybatisPersistentEntityImpl) entity) .getRequiredPersistentEntity( p.getActualType())).stream() .map(ep -> { StringBuilder sb = new StringBuilder(); if (ignoreNull) { sb.append( "<if test=\""); if (byId) { sb.append( "__entity != null and "); sb.append( "__entity." + p.getName() + " != null and "); sb.append( "__entity." + p.getName() + '.' + ep.getName() + " != null"); } else { sb.append(p .getName() + " != null and "); sb.append(p .getName() + '.' + ep.getName() + " != null"); } sb.append("\">"); } sb.append(ep .getColumnName()) .append("="); sb.append((null != ep .getSpecifiedTypeHandler() ? String.format( "#{%s.%s,jdbcType=%s,typeHandler=%s}", byId ? ("__entity." + p.getName()) : p.getName(), ep.getName(), ep.getJdbcType() .name(), ep.getSpecifiedTypeHandler() .getName()) : String.format( "#{%s.%s,jdbcType=%s}", byId ? ("__entity." + p.getName()) : p.getName(), ep.getName(), ep.getJdbcType() .name()))); sb.append(","); if (ignoreNull) { sb.append( "</if>"); } return sb.toString(); }).collect(Collectors .joining(" ")); } if (p.isVersionProperty()) { return p.getColumnName() + "=" + p.getColumnName() + "+1,"; } StringBuilder sb = new StringBuilder(); if (ignoreNull) { if (ignoreNull) { sb.append("<if test=\""); if (byId) { sb.append("__entity != null and "); sb.append("__entity." + p.getName() + " != null"); } else { sb.append(p.getName() + " != null"); } sb.append("\">"); } } sb.append(p.getColumnName()).append("="); sb.append((null != p.getSpecifiedTypeHandler() ? String.format("#{%s,jdbcType=%s,typeHandler=%s}", byId ? ("__entity." + p.getName()) : p.getName(), p.getJdbcType().name(), p.getSpecifiedTypeHandler().getName()) : String.format("#{%s,jdbcType=%s}", byId ? ("__entity." + p.getName()) : p.getName(), p.getJdbcType().name()))); sb.append(","); if (ignoreNull) { sb.append("</if>"); } return sb.toString(); }).collect(Collectors.joining())); builder.append("</set> where ").append(buildIdCaluse(true, byId)); String[] sqls = new String[] { "<script>", builder.toString(), "</script>" }; addMappedStatement( ignoreNull ? (byId ? "__update_by_id_ignore_null" : "__update_ignore_null") : (byId ? "__update_by_id" : "__update"), sqls, UPDATE, entity.getType()); } private void addGetByIdStatement() { StringBuilder builder = new StringBuilder(); builder.append("select * "); // builder.append(findNormalColumns().stream() // .map(p -> String.format("%s as %s", p.getColumnName(), p.getName())) // .collect(Collectors.joining(","))); builder.append(" from ").append(entity.getTableName()).append(" where ") .append(buildIdCaluse(false, false)); addMappedStatement("__get_by_id", new String[] { builder.toString() }, SELECT, entity.getIdProperty().getType(), RESULT_MAP); } private void addCountStatement() { StringBuilder builder = new StringBuilder(); builder.append("select count(*) from ").append(entity.getTableName()).append(" "); // find in if (entity.hasIdProperty()) { builder.append("<if test=\"__ids != null\">"); builder.append("<trim prefix=\" where \" prefixOverrides=\"and |or \">"); builder.append(entity.getIdProperty().getColumnName()).append(" in "); builder.append( "<foreach item=\"item\" index=\"index\" collection=\"__ids\" open=\"(\" separator=\",\" close=\")\">#{item}</foreach>"); builder.append("</trim>"); builder.append("</if>"); } // find condition builder.append("<if test=\"__condition != null\">"); builder.append("<trim prefix=\" where \" prefixOverrides=\"and |or \">"); builder.append(buildCondition()); builder.append("</trim>"); builder.append("</if>"); addMappedStatement("__count", new String[] { "<script>", builder.toString(), "</script>" }, SELECT, entity.getType(), long.class); } private void addCountAllStatement() { StringBuilder builder = new StringBuilder(); builder.append("select count(*) from ").append(entity.getTableName()); addMappedStatement("__count_all", new String[] { builder.toString() }, SELECT, entity.getType(), long.class); } private void addDeleteByIdStatement() { if (!entity.hasIdProperty()) { return; } StringBuilder builder = new StringBuilder(); builder.append("delete from ").append(entity.getTableName()).append(" where ") .append(buildIdCaluse(false, false)); addMappedStatement("__delete_by_id", new String[] { builder.toString() }, DELETE, entity.getIdProperty().getType()); } private void addDeleteAllStatement() { addMappedStatement("__delete_all", new String[] { "delete from " + entity.getTableName() }, DELETE); } private void addFindStatement(boolean pageable) { StringBuilder builder = new StringBuilder(); builder.append("select * "); // builder.append(findNormalColumns().stream() // .map(p -> String.format("%s as %s", p.getColumnName(), p.getName())) // .collect(Collectors.joining(","))); builder.append(" from ").append(entity.getTableName()); // find in if (entity.hasIdProperty()) { builder.append("<if test=\"__ids != null\">"); builder.append("<trim prefix=\" where \" prefixOverrides=\"and |or \">"); builder.append(entity.getIdProperty().getColumnName()).append(" in "); builder.append( "<foreach item=\"item\" index=\"index\" collection=\"__ids\" open=\"(\" separator=\",\" close=\")\">#{item}</foreach>"); builder.append("</trim>"); builder.append("</if>"); } // find condition builder.append("<if test=\"__condition != null\">"); builder.append("<trim prefix=\" where \" prefixOverrides=\"and |or \">"); builder.append(buildCondition()); builder.append("</trim>"); builder.append("</if>"); // order by builder.append(buildStandardOrderBy()); addMappedStatement(pageable ? "__find_by_pager" : "__find", new String[] { "<script>", pageable ? dialect.getLimitHandler().processSql( builder.toString(), null) : builder.toString(), "</script>" }, DELETE, Map.class, RESULT_MAP); } private String buildCondition() { final StringBuilder builder = new StringBuilder(); entity.doWithProperties((PropertyHandler<MybatisPersistentProperty>) p -> { Set<Condition> set = new HashSet<>(); Conditions conditions = p.findAnnotation(Conditions.class); if (null != conditions && conditions.value().length > 0) { set.addAll(Stream.of(conditions.value()).collect(Collectors.toSet())); } Condition condition = p.findAnnotation(Condition.class); if (null != condition) { set.add(condition); } builder.append(set.stream().map(c -> { String[] properties = c.properties(); if (null == properties || properties.length == 0) { properties = new String[] { p.getName() }; } Type type = Type.valueOf(c.type().name()); if (type.getNumberOfArguments() > 0 && type.getNumberOfArguments() != properties.length) { throw new MappingException("@Condition with type " + type + " needs " + type.getNumberOfArguments() + " arguments, but only find " + properties.length + " properties in this @Condition."); } StringBuilder sb = new StringBuilder(); sb.append("<if test=\""); sb.append(Stream.of(properties).map( property -> String.format("__condition.%s != null", property)) .collect(Collectors.joining(" and "))); sb.append("\">"); sb.append(" and ") .append(queryConditionLeft( StringUtils.hasText(c.column()) ? c.column() : p.getColumnName(), IgnoreCaseType.valueOf(c.ignoreCaseType().name()))) .append(calculateOperation(type)); sb.append(queryConditionRight(type, IgnoreCaseType.valueOf(c.ignoreCaseType().name()), Stream.of(properties).map(p1 -> "__condition." + p1) .toArray(String[]::new))); sb.append("</if>"); return sb; }).collect(Collectors.joining(" "))); }); return builder.toString(); } private String buildIdCaluse(boolean clearly, boolean byId) { if (!entity.hasIdProperty()) { return null; } MybatisPersistentProperty p = entity.getRequiredIdProperty(); if (p.isAnnotationPresent(EmbeddedId.class)) { return findNormalColumns(((MybatisPersistentEntityImpl) this.entity) .getRequiredPersistentEntity(p.getActualType())) .stream() .map(ep -> ep.getColumnName() + " = " + (null != ep.getSpecifiedTypeHandler() ? String.format( "#{%s,jdbcType=%s,typeHandler=%s}", clearly ? ((byId ? "__id" : p.getName()) + '.' + ep.getName()) : ep.getName(), ep.getJdbcType().name(), ep.getSpecifiedTypeHandler() .getName()) : String.format("#{%s,jdbcType=%s}", clearly ? ((byId ? "__id" : p.getName()) + '.' + ep.getName()) : ep.getName(), ep.getJdbcType().name()))) .collect(Collectors.joining(" and ")); } return p.getColumnName() + " = " + (null != p.getSpecifiedTypeHandler() ? String.format("#{%s,jdbcType=%s,typeHandler=%s}", byId ? "__id" : p.getName(), p.getJdbcType().name(), p.getSpecifiedTypeHandler().getName()) : String.format("#{%s,jdbcType=%s}", byId ? "__id" : p.getName(), p.getJdbcType().name())); } }