手写jpa自动创建数据库表

背景

最近用sqlite数据库开发本地应用,后端使用的springboot + jpa。现在遇到sqlite对单条sql中select union 最大限制为500,而jpa底层把所有表中的列作为 一个select union 查询并且超过了数据库的最大限制,为了方便以后的开发,运维,所以需要实现jpa自动创建表的功能,减少开发和运维的工作量。

实现逻辑

创建一个类,实现 BeanPostProcessor

为什么要实现BeanPostProcessor呢,因为spring在创建bean的时候,先创建的BFPP,再创建BPP,然后创建内部Bean,最后再创建工程中使用的Bean,使用BPP就是为了防止有冲突出现。

@Component
@Slf4j
@Order(Integer.MIN_VALUE)
public class ComplementBeanPostProcessor implements BeanPostProcessor {
在jpa的实体类上实现自定义接口

JpaEntryDefinition接口为一个空定义接口,只是为了标识JPA的实体,源码能力强的可以通过@Entity加@Table注解获取对应的实体类

@Data
@Entity
@Table(name = "table_name")
public class TableName implements JpaEntryDefinition{
}
创建@PostConstruct中的方法

@PostConstruct在ComplementBeanPostProcessor初始化的时候会被调用

@PostConstruct
public void init(){
	// 初始化数据库表
    verifyAndComplementTable();
}
增加列或者创建表

java类型与数据库类型关系映射

public static final Map<String,String> classTypeMappingColumnType = Maps.newHashMap();
static{
    classTypeMappingColumnType.put("int","integer");
    classTypeMappingColumnType.put("java.lang.Integer","integer");
    classTypeMappingColumnType.put("java.lang.String","varchar(255)");
    classTypeMappingColumnType.put("long","Long");
    classTypeMappingColumnType.put("java.lang.Long","Long");
}
private void verifyAndComplementTable() {
    log.info("start----------------->校验并完善数据库表结构");
    Set<Class<?>> tableList = ReflectUtils.getChildreClass(JpaEntryDefinition.class);
    validJpaEntryDefinition(tableList);
    //获取数据库中所有的表
    List<String> existTableNme = jdbcTemplate.queryForList("select name from sqlite_master where type='table' order by name ",String.class);
    Map<String, String> existTableNmeMap = existTableNme.stream().collect(Collectors.toMap(Function.identity(), Function.identity()));
    for(Class clazz : tableList){
        Table tableAnno = (Table)clazz.getAnnotation(Table.class);
        String tableName = tableAnno.name();
        if(existTableNmeMap.containsKey(tableName)){
            // 表已存在
            //获取表的列
            List<String> tableColumn = getTableColumn(tableName);
            Map<String, String> tableColumnMap = tableColumn.stream().collect(Collectors.toMap(Function.identity(), Function.identity()));
            Field[] fields = clazz.getDeclaredFields();
            for(Field field : fields){
                field.setAccessible(true);
                JoinColumn joinColumAnno = field.getAnnotation(JoinColumn.class);
                Transient transientAnno = field.getAnnotation(Transient.class);
                // 不需要增加的列
                if(!Objects.isNull(joinColumAnno) || !Objects.isNull(transientAnno)){
                    continue;
                }
                String columnName = field.getName();
                // @Column优先级比较高
                Column columNann = field.getAnnotation(Column.class);
                if(!Objects.isNull(columNann) && StringUtils.isNotBlank(columNann.name())){
                    columnName = columNann.name();
                }
                // 已经存在的列
                if(tableColumnMap.containsKey(columnName)){
                    continue;
                }
                // 数据库中不存在的列需要添加到数据库
                Class<?> type = field.getType();
                String columnType = classTypeMappingColumnType.get(type.getName());
                String sql = "ALTER TABLE ".concat(tableName).concat(" ADD COLUMN ").concat(columnName).concat(" ").concat(columnType);
                log.info("--------> 添加表结构列,table={},columnName={},sql = {}",tableName,columnName,sql);
                jdbcTemplate.update(sql);
            }
        }else{
            // 不存在此表,创建表
            StringBuilder sb = new StringBuilder();
            sb.append("CREATE TABLE ").append(tableName).append(" ( ");
            Field[] fields = clazz.getDeclaredFields();
            String primaryKey = "PRIMARY KEY (";
            for(Field field : fields){
                field.setAccessible(true);
                JoinColumn joinColumAnno = field.getAnnotation(JoinColumn.class);
                Transient transientAnno = field.getAnnotation(Transient.class);
                // 不需要增加的列
                if(!Objects.isNull(joinColumAnno) || !Objects.isNull(transientAnno)){
                    continue;
                }
                String columnName = field.getName();
                // @Column优先级比较高
                Column columNann = field.getAnnotation(Column.class);
                if(!Objects.isNull(columNann) && StringUtils.isNotBlank(columNann.name())){
                    columnName = columNann.name();
                }
                String columnType = classTypeMappingColumnType.get(field.getType().getName());
                Id idColumAnno = field.getAnnotation(Id.class);
                if(!Objects.isNull(idColumAnno)){
                    sb.append(columnName).append(" ").append(columnType).append(" NOT NULL, ");
                    primaryKey = primaryKey + columnName + ") ";
                }else{
                    sb.append(columnName).append(" ").append(columnType).append(" , ");
                }
            }
            sb.append(primaryKey);
            sb.append("); ");
            log.info("--------> 添加表 table={},sql = {}",tableName,sb.toString());
            jdbcTemplate.update(sb.toString());
        }
    }
    log.info("end----------------->校验并完善数据库表结构");
}
校验规则 validJpaEntryDefinition(tableList);
private void validJpaEntryDefinition(Set<Class<?>> tableList){
   Map<String,Class> validRepetitionMap = Maps.newHashMap();
   for(Class clazz : tableList){
       Entity entityAnno = (Entity)clazz.getAnnotation(Entity.class);
       Table tableAnno = (Table)clazz.getAnnotation(Table.class);
       if(Objects.isNull(entityAnno) || Objects.isNull(tableAnno)){
           throw new AnnotationException("JpaEntryDefinition 的实现类必须存在 @Table 和 @Entity 注解");
       }
       String tableName = tableAnno.name();
       if(StringUtils.isBlank(tableName)){
           throw new AnnotationException("JpaEntryDefinition 的实现类注解@Table,name属性必须存在,className="+clazz.getName());
       }
       if(validRepetitionMap.containsKey(tableName)){
           throw new AnnotationException("JpaEntryDefinition 的实现类,重复的table名称,className="+clazz.getName()+"   ,已存在的className="+validRepetitionMap.get(tableName).getName());
       }
       validRepetitionMap.put(tableName,clazz);
       // 表内列名 jpa已经验证好,无需重复校验
       // 验证实体中必须存在属性,也就是数据库的列
       Field[] fields = clazz.getDeclaredFields();
       if(Objects.isNull(fields) || fields.length == 0){
           throw new AnnotationException("JpaEntryDefinition 的实现类必须存在属性,className="+clazz.getName());
       }
       Set<String> properties = new HashSet<>();
       Id idNano = null;
       for(Field field : fields){
           String fieldName = field.getName();
           Column columNann = field.getAnnotation(Column.class);
           if(!Objects.isNull(columNann) && StringUtils.isNotBlank(columNann.name())){
               fieldName = columNann.name();
           }
           JoinColumn joinColumAnno = field.getAnnotation(JoinColumn.class);
           Transient transientAnno = field.getAnnotation(Transient.class);
           if(!Objects.isNull(joinColumAnno) || !Objects.isNull(transientAnno)){
               continue;
           }
           Id idNan = field.getAnnotation(Id.class);
           if(!Objects.isNull(idNan)){
               idNano = idNan;
           }
           properties.add(fieldName);
       }
       if(Objects.isNull(idNano)){
           throw new AnnotationException("JpaEntryDefinition 的实现类必须存在主键,className="+clazz.getName());
       }
       if(properties.size() == 0){
           // 表中必须要有列
           throw new AnnotationException("JpaEntryDefinition 的实现类必须存在非@JoinColumn、@Transient注解修饰的列 ,className="+clazz.getName());
       }
   }
}

总结

这种方式逻辑支持大部分场景,如果遇到这种情况需要使用到这里的代码,需要完善细节,如:数据库字段类型映射,注解的结束等

最后,希望对大家有有所帮助…

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值