项目场景
基于 Spring Boot 3.x.x
引入 spring-boot-starter-data-jpa-3.x.x.jar
问题描述
项目中配置了:spring.jpa.hibernate.ddl-auto=update
当使用Spring Data JPA自动生成表的时候,所产生的列顺序与Entity类中定义的顺序不一致,会按照字母序来定义列的顺序。
当查看表中数据时,会有点不太方便;
原因分析
Hibernate 源码中的PropertyContainer类,里面的逻辑使用了无序的数据结构,即:TreeMap。
解决方案
解决思路: 替换Hibernate的实现,相关逻辑换成有序的数据结构,即:LinkedHashMap
老版本解决方案
- 在工程中新建
org.hibernate.cfg包 - 找到hibernate-core包下的
org.hibernate.cfg包下的PropertyContainer类,复制到本工程的org.hibernate.cfg包下 - 把PropertyContainer类中定义的persistentAttributeMap类型,从TreeMap修改为LinkedHashMap
新版本解决方案
注意:
因为 spring-boot-starter-data-jpa-3.x.x.jar 中,依赖的Hibernate版本已经是6.x.x 了,这个PropertyContainer类已经被修改,具体的可以到github上查看,所以在具体解决时,需要注意新老版本的不同;
在我处理的版本里,相关代码如下:
public PropertyContainer(XClass clazz, XClass entityAtStake, AccessType defaultClassLevelAccessType) {
this.xClass = clazz;
this.entityAtStake = entityAtStake;
if ( defaultClassLevelAccessType == AccessType.DEFAULT ) {
// this is effectively what the old code did when AccessType.DEFAULT was passed in
// to getProperties(AccessType) from AnnotationBinder and InheritanceState
defaultClassLevelAccessType = AccessType.PROPERTY;
}
AccessType localClassLevelAccessType = determineLocalClassDefinedAccessStrategy();
assert localClassLevelAccessType != null;
this.classLevelAccessType = localClassLevelAccessType != AccessType.DEFAULT
? localClassLevelAccessType
: defaultClassLevelAccessType;
assert classLevelAccessType == AccessType.FIELD || classLevelAccessType == AccessType.PROPERTY
|| classLevelAccessType == AccessType.RECORD;
final List<XProperty> fields = xClass.getDeclaredProperties( AccessType.FIELD.getType() );
final List<XProperty> getters = xClass.getDeclaredProperties( AccessType.PROPERTY.getType() );
final List<XProperty> recordComponents = xClass.getDeclaredProperties( AccessType.RECORD.getType() );
preFilter( fields, getters, recordComponents );
final Map<String,XProperty> persistentAttributesFromGetters = new HashMap<>();
final Map<String,XProperty> persistentAttributesFromComponents = new HashMap<>();
final Map<String, XProperty> localAttributeMap;
// If the record class has only record components which match up with fields and no additional getters,
// we can retain the property order, to match up with the record component order
if ( !recordComponents.isEmpty() && recordComponents.size() == fields.size() && getters.isEmpty() ) {
localAttributeMap = new LinkedHashMap<>();
}
//otherwise we sort them in alphabetical order, since this is at least deterministic
else {
localAttributeMap = new TreeMap<>();
}
collectPersistentAttributesUsingLocalAccessType(
xClass,
localAttributeMap,
persistentAttributesFromGetters,
persistentAttributesFromComponents,
fields,
getters,
recordComponents
);
collectPersistentAttributesUsingClassLevelAccessType(
xClass,
classLevelAccessType,
localAttributeMap,
persistentAttributesFromGetters,
persistentAttributesFromComponents,
fields,
getters,
recordComponents
);
this.persistentAttributes = verifyAndInitializePersistentAttributes( xClass, localAttributeMap );
}
通过上面的源码,大家可以看到这段判断逻辑:
final Map<String, XProperty> localAttributeMap;
// If the record class has only record components which match up with fields and no additional getters,
// we can retain the property order, to match up with the record component order
if ( !recordComponents.isEmpty() && recordComponents.size() == fields.size() && getters.isEmpty() ) {
localAttributeMap = new LinkedHashMap<>();
}
//otherwise we sort them in alphabetical order, since this is at least deterministic
else {
localAttributeMap = new TreeMap<>();
}
如果条件满足的话,localAttributeMap 的类型就是 LinkedHashMap,则不会出现问题;
判断条件解释如下:
如果定义的Entity类是 Record类型,就会通过调用 Class 类的 getRecordComponents 方法获取 java.lang.reflect.RecordComponent,然后判断它的数量是否和字段数量相同,且没有getter方法。
注:这里的Record类型 指的是 java 14的新特性,如果不了解的童鞋可以看下面的链接
如果不满足的情况下,即还是 TreeMap。而我现在遇到的情况,就是这种,因为我的项目中定义Entity使用的是Class,且有getter和setter等方法,所以还是需要使用下面的步骤来解决。
解决步骤:
- 在工程中新建
org.hibernate.boot.model.internal包 - 找到hibernate-core包下的
org.hibernate.boot.model.internal下的PropertyContainer类,复制到本工程的org.hibernate.boot.model.internal包下 - 把PropertyContainer类中,定义的
localAttributeMap = new TreeMap<>();修改为localAttributeMap = new LinkedHashMap<>();
以上就是我的分享,实测可以解决,如果大家有问题,可以留言交流,欢迎大家点赞和分享,谢谢。