Java 如何使用Hibernate+;JPA

Java 如何使用Hibernate+;JPA,java,spring,hibernate,jpa,Java,Spring,Hibernate,Jpa,我有两个实体空间和类型。它们都是相互联系的。使用这些对象,当代码执行甚至非常简单的操作时,我会遇到许多不需要的update语句 我在下面写一个简单的场景。但是,还有一些更复杂的批处理操作正在我的应用程序(SpringBootAPI)中执行。而且,此问题导致所有链接实体都被更新,即使它们未被修改 我需要以某种方式消除这些不必要的更新,因为它们会给某些操作带来很大的性能问题 空间实体(部分显示): @Entity @Table(name = "spaces") @Getter @

我有两个实体空间和类型。它们都是相互联系的。使用这些对象,当代码执行甚至非常简单的操作时,我会遇到许多不需要的update语句

我在下面写一个简单的场景。但是,还有一些更复杂的批处理操作正在我的应用程序(SpringBootAPI)中执行。而且,此问题导致所有链接实体都被更新,即使它们未被修改

我需要以某种方式消除这些不必要的更新,因为它们会给某些操作带来很大的性能问题

空间实体(部分显示):

@Entity
@Table(name = "spaces")
@Getter
@Setter
@NoArgsConstructor
public class SpaceDao {
        @Id
        @GeneratedValue(generator = "uuid")
        @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
        private byte[] uuid;

        @ManyToOne
        @JoinColumn(name = "type_id")
        private TypeDao type;
}
@Entity
@Table(name = "types")
@Getter
@Setter
@NoArgsConstructor
public class TypeDao {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
  
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="space_id")
    private SpaceDao space;
}
public Space saveSpace(Space space) {
    SpaceDao spaceDao = SpaceMapper.toDao(space);

    // Intentionally simplified this logic, to point out that
    // I am only reading and saving the object, without any changes.
    SpaceDao existingSpaceDao = relationalSpaceRepository.findById(spaceDao.getUuid()).get();
    // Following line is where the magic happens
    SpaceDao savedSpaceDao = relationalSpaceRepository.save(existingSpaceDao);

    return SpaceMapper.toModelObject(savedSpaceDao, true);
}
public interface RelationalSpaceRepository extends CrudRepository<SpaceDao, byte[]> { }
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
@Component
public class CustomHibernateInterceptor extends EmptyInterceptor {

    private static final long serialVersionUID = -2355165114530619983L;

    @Override
    public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
            String[] propertyNames, Type[] types) {
        if (entity instanceof BaseEntity) {
            Set<String> dirtyProperties = new HashSet<>();
            for (int i = 0; i < propertyNames.length; i++) {
                if (isModified(currentState, previousState, types, i)) {
                    dirtyProperties.add(propertyNames[i]);
                }
            }

            int[] dirtyPropertiesIndices = new int[dirtyProperties.size()];
            List<String> propertyNamesList = Arrays.asList(propertyNames);
            int i = 0;
            for (String dirtyProperty : dirtyProperties) {
                dirtyPropertiesIndices[i++] = propertyNamesList.indexOf(dirtyProperty);
            }
            return dirtyPropertiesIndices;
        }

        return super.findDirty(entity, id, currentState, previousState, propertyNames, types);
    }

    private boolean isModified(Object[] currentState, Object[] previousState, Type[] types, int i) {
        boolean equals = true;
        Object oldValue = previousState[i];
        Object newValue = currentState[i];

        if (oldValue != null || newValue != null) {
            if (types[i] instanceof AttributeConverterTypeAdapter) {
                // check for JSONObject attributes
                equals = String.valueOf(oldValue).equals(String.valueOf(newValue));
            } else if (types[i] instanceof BinaryType) {
                // byte arrays in our entities are always UUID representations
                equals = Utilities.byteArrayToUUID((byte[]) oldValue)
                        .equals(Utilities.byteArrayToUUID((byte[]) newValue));
            } else if (!(types[i] instanceof CollectionType)) {
                equals = Objects.equals(oldValue, newValue);
            }
        }

        return !equals;
    }
}
@Configuration
public class XDatabaseConfig {

    @Bean(name = "xEntityManagerFactory")
    @Primary
    public EntityManagerFactory entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        CustomHibernateInterceptor interceptor = new CustomHibernateInterceptor();
        vendorAdapter.setGenerateDdl(Boolean.FALSE);
        vendorAdapter.setShowSql(Boolean.TRUE);
        vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.x.dal.relational.model");
        factory.setDataSource(xDataSource());
        factory.getJpaPropertyMap().put("hibernate.session_factory.interceptor", interceptor);
        factory.afterPropertiesSet();
        factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
        return factory.getObject();
    }

}
类型实体(部分显示):

@Entity
@Table(name = "spaces")
@Getter
@Setter
@NoArgsConstructor
public class SpaceDao {
        @Id
        @GeneratedValue(generator = "uuid")
        @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
        private byte[] uuid;

        @ManyToOne
        @JoinColumn(name = "type_id")
        private TypeDao type;
}
@Entity
@Table(name = "types")
@Getter
@Setter
@NoArgsConstructor
public class TypeDao {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
  
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="space_id")
    private SpaceDao space;
}
public Space saveSpace(Space space) {
    SpaceDao spaceDao = SpaceMapper.toDao(space);

    // Intentionally simplified this logic, to point out that
    // I am only reading and saving the object, without any changes.
    SpaceDao existingSpaceDao = relationalSpaceRepository.findById(spaceDao.getUuid()).get();
    // Following line is where the magic happens
    SpaceDao savedSpaceDao = relationalSpaceRepository.save(existingSpaceDao);

    return SpaceMapper.toModelObject(savedSpaceDao, true);
}
public interface RelationalSpaceRepository extends CrudRepository<SpaceDao, byte[]> { }
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
@Component
public class CustomHibernateInterceptor extends EmptyInterceptor {

    private static final long serialVersionUID = -2355165114530619983L;

    @Override
    public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
            String[] propertyNames, Type[] types) {
        if (entity instanceof BaseEntity) {
            Set<String> dirtyProperties = new HashSet<>();
            for (int i = 0; i < propertyNames.length; i++) {
                if (isModified(currentState, previousState, types, i)) {
                    dirtyProperties.add(propertyNames[i]);
                }
            }

            int[] dirtyPropertiesIndices = new int[dirtyProperties.size()];
            List<String> propertyNamesList = Arrays.asList(propertyNames);
            int i = 0;
            for (String dirtyProperty : dirtyProperties) {
                dirtyPropertiesIndices[i++] = propertyNamesList.indexOf(dirtyProperty);
            }
            return dirtyPropertiesIndices;
        }

        return super.findDirty(entity, id, currentState, previousState, propertyNames, types);
    }

    private boolean isModified(Object[] currentState, Object[] previousState, Type[] types, int i) {
        boolean equals = true;
        Object oldValue = previousState[i];
        Object newValue = currentState[i];

        if (oldValue != null || newValue != null) {
            if (types[i] instanceof AttributeConverterTypeAdapter) {
                // check for JSONObject attributes
                equals = String.valueOf(oldValue).equals(String.valueOf(newValue));
            } else if (types[i] instanceof BinaryType) {
                // byte arrays in our entities are always UUID representations
                equals = Utilities.byteArrayToUUID((byte[]) oldValue)
                        .equals(Utilities.byteArrayToUUID((byte[]) newValue));
            } else if (!(types[i] instanceof CollectionType)) {
                equals = Objects.equals(oldValue, newValue);
            }
        }

        return !equals;
    }
}
@Configuration
public class XDatabaseConfig {

    @Bean(name = "xEntityManagerFactory")
    @Primary
    public EntityManagerFactory entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        CustomHibernateInterceptor interceptor = new CustomHibernateInterceptor();
        vendorAdapter.setGenerateDdl(Boolean.FALSE);
        vendorAdapter.setShowSql(Boolean.TRUE);
        vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.x.dal.relational.model");
        factory.setDataSource(xDataSource());
        factory.getJpaPropertyMap().put("hibernate.session_factory.interceptor", interceptor);
        factory.afterPropertiesSet();
        factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
        return factory.getObject();
    }

}
空间回购实施中的保存方法:

@Entity
@Table(name = "spaces")
@Getter
@Setter
@NoArgsConstructor
public class SpaceDao {
        @Id
        @GeneratedValue(generator = "uuid")
        @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
        private byte[] uuid;

        @ManyToOne
        @JoinColumn(name = "type_id")
        private TypeDao type;
}
@Entity
@Table(name = "types")
@Getter
@Setter
@NoArgsConstructor
public class TypeDao {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
  
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="space_id")
    private SpaceDao space;
}
public Space saveSpace(Space space) {
    SpaceDao spaceDao = SpaceMapper.toDao(space);

    // Intentionally simplified this logic, to point out that
    // I am only reading and saving the object, without any changes.
    SpaceDao existingSpaceDao = relationalSpaceRepository.findById(spaceDao.getUuid()).get();
    // Following line is where the magic happens
    SpaceDao savedSpaceDao = relationalSpaceRepository.save(existingSpaceDao);

    return SpaceMapper.toModelObject(savedSpaceDao, true);
}
public interface RelationalSpaceRepository extends CrudRepository<SpaceDao, byte[]> { }
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
@Component
public class CustomHibernateInterceptor extends EmptyInterceptor {

    private static final long serialVersionUID = -2355165114530619983L;

    @Override
    public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
            String[] propertyNames, Type[] types) {
        if (entity instanceof BaseEntity) {
            Set<String> dirtyProperties = new HashSet<>();
            for (int i = 0; i < propertyNames.length; i++) {
                if (isModified(currentState, previousState, types, i)) {
                    dirtyProperties.add(propertyNames[i]);
                }
            }

            int[] dirtyPropertiesIndices = new int[dirtyProperties.size()];
            List<String> propertyNamesList = Arrays.asList(propertyNames);
            int i = 0;
            for (String dirtyProperty : dirtyProperties) {
                dirtyPropertiesIndices[i++] = propertyNamesList.indexOf(dirtyProperty);
            }
            return dirtyPropertiesIndices;
        }

        return super.findDirty(entity, id, currentState, previousState, propertyNames, types);
    }

    private boolean isModified(Object[] currentState, Object[] previousState, Type[] types, int i) {
        boolean equals = true;
        Object oldValue = previousState[i];
        Object newValue = currentState[i];

        if (oldValue != null || newValue != null) {
            if (types[i] instanceof AttributeConverterTypeAdapter) {
                // check for JSONObject attributes
                equals = String.valueOf(oldValue).equals(String.valueOf(newValue));
            } else if (types[i] instanceof BinaryType) {
                // byte arrays in our entities are always UUID representations
                equals = Utilities.byteArrayToUUID((byte[]) oldValue)
                        .equals(Utilities.byteArrayToUUID((byte[]) newValue));
            } else if (!(types[i] instanceof CollectionType)) {
                equals = Objects.equals(oldValue, newValue);
            }
        }

        return !equals;
    }
}
@Configuration
public class XDatabaseConfig {

    @Bean(name = "xEntityManagerFactory")
    @Primary
    public EntityManagerFactory entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        CustomHibernateInterceptor interceptor = new CustomHibernateInterceptor();
        vendorAdapter.setGenerateDdl(Boolean.FALSE);
        vendorAdapter.setShowSql(Boolean.TRUE);
        vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.x.dal.relational.model");
        factory.setDataSource(xDataSource());
        factory.getJpaPropertyMap().put("hibernate.session_factory.interceptor", interceptor);
        factory.afterPropertiesSet();
        factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
        return factory.getObject();
    }

}
空间积垢存储库:

@Entity
@Table(name = "spaces")
@Getter
@Setter
@NoArgsConstructor
public class SpaceDao {
        @Id
        @GeneratedValue(generator = "uuid")
        @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
        private byte[] uuid;

        @ManyToOne
        @JoinColumn(name = "type_id")
        private TypeDao type;
}
@Entity
@Table(name = "types")
@Getter
@Setter
@NoArgsConstructor
public class TypeDao {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
  
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="space_id")
    private SpaceDao space;
}
public Space saveSpace(Space space) {
    SpaceDao spaceDao = SpaceMapper.toDao(space);

    // Intentionally simplified this logic, to point out that
    // I am only reading and saving the object, without any changes.
    SpaceDao existingSpaceDao = relationalSpaceRepository.findById(spaceDao.getUuid()).get();
    // Following line is where the magic happens
    SpaceDao savedSpaceDao = relationalSpaceRepository.save(existingSpaceDao);

    return SpaceMapper.toModelObject(savedSpaceDao, true);
}
public interface RelationalSpaceRepository extends CrudRepository<SpaceDao, byte[]> { }
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
@Component
public class CustomHibernateInterceptor extends EmptyInterceptor {

    private static final long serialVersionUID = -2355165114530619983L;

    @Override
    public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
            String[] propertyNames, Type[] types) {
        if (entity instanceof BaseEntity) {
            Set<String> dirtyProperties = new HashSet<>();
            for (int i = 0; i < propertyNames.length; i++) {
                if (isModified(currentState, previousState, types, i)) {
                    dirtyProperties.add(propertyNames[i]);
                }
            }

            int[] dirtyPropertiesIndices = new int[dirtyProperties.size()];
            List<String> propertyNamesList = Arrays.asList(propertyNames);
            int i = 0;
            for (String dirtyProperty : dirtyProperties) {
                dirtyPropertiesIndices[i++] = propertyNamesList.indexOf(dirtyProperty);
            }
            return dirtyPropertiesIndices;
        }

        return super.findDirty(entity, id, currentState, previousState, propertyNames, types);
    }

    private boolean isModified(Object[] currentState, Object[] previousState, Type[] types, int i) {
        boolean equals = true;
        Object oldValue = previousState[i];
        Object newValue = currentState[i];

        if (oldValue != null || newValue != null) {
            if (types[i] instanceof AttributeConverterTypeAdapter) {
                // check for JSONObject attributes
                equals = String.valueOf(oldValue).equals(String.valueOf(newValue));
            } else if (types[i] instanceof BinaryType) {
                // byte arrays in our entities are always UUID representations
                equals = Utilities.byteArrayToUUID((byte[]) oldValue)
                        .equals(Utilities.byteArrayToUUID((byte[]) newValue));
            } else if (!(types[i] instanceof CollectionType)) {
                equals = Objects.equals(oldValue, newValue);
            }
        }

        return !equals;
    }
}
@Configuration
public class XDatabaseConfig {

    @Bean(name = "xEntityManagerFactory")
    @Primary
    public EntityManagerFactory entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        CustomHibernateInterceptor interceptor = new CustomHibernateInterceptor();
        vendorAdapter.setGenerateDdl(Boolean.FALSE);
        vendorAdapter.setShowSql(Boolean.TRUE);
        vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.x.dal.relational.model");
        factory.setDataSource(xDataSource());
        factory.getJpaPropertyMap().put("hibernate.session_factory.interceptor", interceptor);
        factory.afterPropertiesSet();
        factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
        return factory.getObject();
    }

}

找到了原因和解决方案

这是由假阳性的脏支票引起的。我不太清楚为什么,但在从数据库检索实体之后,我相信引用类型的属性值会被重新实例化。导致脏检查将这些实体视为已修改。因此,下次刷新上下文时,所有“修改”的实体都将被持久化到数据库中

作为解决方案,我实现了一个定制的Hibernate拦截器,扩展了EmptyInterceptor。并将其注册为
hibernate.session\u factory.interceptor
。这样,我就可以进行自定义比较并手动计算脏标志

拦截器实现:

@Entity
@Table(name = "spaces")
@Getter
@Setter
@NoArgsConstructor
public class SpaceDao {
        @Id
        @GeneratedValue(generator = "uuid")
        @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
        private byte[] uuid;

        @ManyToOne
        @JoinColumn(name = "type_id")
        private TypeDao type;
}
@Entity
@Table(name = "types")
@Getter
@Setter
@NoArgsConstructor
public class TypeDao {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
  
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="space_id")
    private SpaceDao space;
}
public Space saveSpace(Space space) {
    SpaceDao spaceDao = SpaceMapper.toDao(space);

    // Intentionally simplified this logic, to point out that
    // I am only reading and saving the object, without any changes.
    SpaceDao existingSpaceDao = relationalSpaceRepository.findById(spaceDao.getUuid()).get();
    // Following line is where the magic happens
    SpaceDao savedSpaceDao = relationalSpaceRepository.save(existingSpaceDao);

    return SpaceMapper.toModelObject(savedSpaceDao, true);
}
public interface RelationalSpaceRepository extends CrudRepository<SpaceDao, byte[]> { }
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
@Component
public class CustomHibernateInterceptor extends EmptyInterceptor {

    private static final long serialVersionUID = -2355165114530619983L;

    @Override
    public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
            String[] propertyNames, Type[] types) {
        if (entity instanceof BaseEntity) {
            Set<String> dirtyProperties = new HashSet<>();
            for (int i = 0; i < propertyNames.length; i++) {
                if (isModified(currentState, previousState, types, i)) {
                    dirtyProperties.add(propertyNames[i]);
                }
            }

            int[] dirtyPropertiesIndices = new int[dirtyProperties.size()];
            List<String> propertyNamesList = Arrays.asList(propertyNames);
            int i = 0;
            for (String dirtyProperty : dirtyProperties) {
                dirtyPropertiesIndices[i++] = propertyNamesList.indexOf(dirtyProperty);
            }
            return dirtyPropertiesIndices;
        }

        return super.findDirty(entity, id, currentState, previousState, propertyNames, types);
    }

    private boolean isModified(Object[] currentState, Object[] previousState, Type[] types, int i) {
        boolean equals = true;
        Object oldValue = previousState[i];
        Object newValue = currentState[i];

        if (oldValue != null || newValue != null) {
            if (types[i] instanceof AttributeConverterTypeAdapter) {
                // check for JSONObject attributes
                equals = String.valueOf(oldValue).equals(String.valueOf(newValue));
            } else if (types[i] instanceof BinaryType) {
                // byte arrays in our entities are always UUID representations
                equals = Utilities.byteArrayToUUID((byte[]) oldValue)
                        .equals(Utilities.byteArrayToUUID((byte[]) newValue));
            } else if (!(types[i] instanceof CollectionType)) {
                equals = Objects.equals(oldValue, newValue);
            }
        }

        return !equals;
    }
}
@Configuration
public class XDatabaseConfig {

    @Bean(name = "xEntityManagerFactory")
    @Primary
    public EntityManagerFactory entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        CustomHibernateInterceptor interceptor = new CustomHibernateInterceptor();
        vendorAdapter.setGenerateDdl(Boolean.FALSE);
        vendorAdapter.setShowSql(Boolean.TRUE);
        vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.x.dal.relational.model");
        factory.setDataSource(xDataSource());
        factory.getJpaPropertyMap().put("hibernate.session_factory.interceptor", interceptor);
        factory.afterPropertiesSet();
        factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
        return factory.getObject();
    }

}

找到了原因和解决方案

这是由假阳性的脏支票引起的。我不太清楚为什么,但在从数据库检索实体之后,我相信引用类型的属性值会被重新实例化。导致脏检查将这些实体视为已修改。因此,下次刷新上下文时,所有“修改”的实体都将被持久化到数据库中

作为解决方案,我实现了一个定制的Hibernate拦截器,扩展了EmptyInterceptor。并将其注册为
hibernate.session\u factory.interceptor
。这样,我就可以进行自定义比较并手动计算脏标志

拦截器实现:

@Entity
@Table(name = "spaces")
@Getter
@Setter
@NoArgsConstructor
public class SpaceDao {
        @Id
        @GeneratedValue(generator = "uuid")
        @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
        private byte[] uuid;

        @ManyToOne
        @JoinColumn(name = "type_id")
        private TypeDao type;
}
@Entity
@Table(name = "types")
@Getter
@Setter
@NoArgsConstructor
public class TypeDao {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
  
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="space_id")
    private SpaceDao space;
}
public Space saveSpace(Space space) {
    SpaceDao spaceDao = SpaceMapper.toDao(space);

    // Intentionally simplified this logic, to point out that
    // I am only reading and saving the object, without any changes.
    SpaceDao existingSpaceDao = relationalSpaceRepository.findById(spaceDao.getUuid()).get();
    // Following line is where the magic happens
    SpaceDao savedSpaceDao = relationalSpaceRepository.save(existingSpaceDao);

    return SpaceMapper.toModelObject(savedSpaceDao, true);
}
public interface RelationalSpaceRepository extends CrudRepository<SpaceDao, byte[]> { }
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
Hibernate: update types set category=?, definition=?, description=?, disabled=?, logical_order=?, name=?, space_id=? where id=?
Hibernate: update spaces set description=?, location=?, name=?, parent_id=?, properties=?, status_id=?, status=?, subtype_id=?, subtype=?, type_id=?, type=? where uuid=?
@Component
public class CustomHibernateInterceptor extends EmptyInterceptor {

    private static final long serialVersionUID = -2355165114530619983L;

    @Override
    public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
            String[] propertyNames, Type[] types) {
        if (entity instanceof BaseEntity) {
            Set<String> dirtyProperties = new HashSet<>();
            for (int i = 0; i < propertyNames.length; i++) {
                if (isModified(currentState, previousState, types, i)) {
                    dirtyProperties.add(propertyNames[i]);
                }
            }

            int[] dirtyPropertiesIndices = new int[dirtyProperties.size()];
            List<String> propertyNamesList = Arrays.asList(propertyNames);
            int i = 0;
            for (String dirtyProperty : dirtyProperties) {
                dirtyPropertiesIndices[i++] = propertyNamesList.indexOf(dirtyProperty);
            }
            return dirtyPropertiesIndices;
        }

        return super.findDirty(entity, id, currentState, previousState, propertyNames, types);
    }

    private boolean isModified(Object[] currentState, Object[] previousState, Type[] types, int i) {
        boolean equals = true;
        Object oldValue = previousState[i];
        Object newValue = currentState[i];

        if (oldValue != null || newValue != null) {
            if (types[i] instanceof AttributeConverterTypeAdapter) {
                // check for JSONObject attributes
                equals = String.valueOf(oldValue).equals(String.valueOf(newValue));
            } else if (types[i] instanceof BinaryType) {
                // byte arrays in our entities are always UUID representations
                equals = Utilities.byteArrayToUUID((byte[]) oldValue)
                        .equals(Utilities.byteArrayToUUID((byte[]) newValue));
            } else if (!(types[i] instanceof CollectionType)) {
                equals = Objects.equals(oldValue, newValue);
            }
        }

        return !equals;
    }
}
@Configuration
public class XDatabaseConfig {

    @Bean(name = "xEntityManagerFactory")
    @Primary
    public EntityManagerFactory entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        CustomHibernateInterceptor interceptor = new CustomHibernateInterceptor();
        vendorAdapter.setGenerateDdl(Boolean.FALSE);
        vendorAdapter.setShowSql(Boolean.TRUE);
        vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.x.dal.relational.model");
        factory.setDataSource(xDataSource());
        factory.getJpaPropertyMap().put("hibernate.session_factory.interceptor", interceptor);
        factory.afterPropertiesSet();
        factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
        return factory.getObject();
    }

}

需要更多信息,比如这些检测到的差异来自何处。我怀疑使用find读取的SpaceDao在调用find和调用save之间会变得过时,如果使用类似于合并的操作,这是一个问题。您需要确保保留最初在SpaceDao中读取的上下文,以用于保存操作,因为它可以用来跟踪您所做的实际差异,而不必在调用save时检测它与数据库中当前内容之间的差异。因此,每当您对会话中的托管实体应用某些更改时,在刷新/关闭会话时,这些更改将被持久化。您确定更新不是来自您的会话管理吗?编辑:但看起来您没有应用任何更改…SpaceDao与TypeDao有多对一关系,type与SpaceDao也有多对一关系,这种关系是正确的还是多对多关系?两倍多对一关系非常可疑。另外,只在一侧使用@JoinColumn(多个一侧)。@Chris,它很简单,如repo实现方法所示。这是不符合逻辑的,但我首先对其进行了简化,以测试本案例中可能的解决方案。如图所示,在读和写之间没有修改或任何其他操作。另外,findById()和save()方法是Spring数据的Crudepository中的默认方法。需要更多信息,比如这些检测到的差异来自何处。我怀疑使用find读取的SpaceDao在调用find和调用save之间会变得过时,如果使用类似于合并的操作,这是一个问题。您需要确保保留最初在SpaceDao中读取的上下文,以用于保存操作,因为它可以用来跟踪您所做的实际差异,而不必在调用save时检测它与数据库中当前内容之间的差异。因此,每当您对会话中的托管实体应用某些更改时,在刷新/关闭会话时,这些更改将被持久化。您确定更新不是来自您的会话管理吗?编辑:但看起来您没有应用任何更改…SpaceDao与TypeDao有多对一关系,type与SpaceDao也有多对一关系,这种关系是正确的还是多对多关系?两倍多对一关系非常可疑。另外,只在一侧使用@JoinColumn(多个一侧)。@Chris,它很简单,如repo实现方法所示。这是不符合逻辑的,但我首先对其进行了简化,以测试本案例中可能的解决方案。如图所示,在读和写之间没有修改或任何其他操作。此外,findById()和save()方法是Spring数据的crudepository的默认方法。