背景
最近用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());
}
}
}
总结
这种方式逻辑支持大部分场景,如果遇到这种情况需要使用到这里的代码,需要完善细节,如:数据库字段类型映射,注解的结束等
最后,希望对大家有有所帮助…