【问题标题】:Spring boot JPA doesn't attach entities to the sessionSpring Boot JPA 不会将实体附加到会话
【发布时间】:2017-08-10 07:05:00
【问题描述】:

我有一个使用 Spring Boot JPA(spring-boot-starter-data-jpa 依赖项)的项目,它使用 Hibernate 作为 JPA 实现。

我已自动配置容器 (@EnableAutoConfiguration),并且我使用 EntityManager 进行 CRUD 操作。

问题: 我在启动时使用此 EntityManager 通过 HQL 查询加载我的实体,但是当我想编辑或删除其中任何一个时,我收到以下错误

org.springframework.dao.InvalidDataAccessApiUsageException: 移除一个分离的实例 com.phistory.data.model.car.Car#2;嵌套异常是 java.lang.IllegalArgumentException: Removing a detached instance com.phistory.data.model.car.Car#2

org.springframework.dao.InvalidDataAccessApiUsageException:实体不受管理;嵌套异常是 java.lang.IllegalArgumentException: Entity not managed

图书馆:

  • spring-boot-starter-data-jpa 1.4.4.RELEASE (Hibernate 5.0.11.Final)

主要:

@SpringBootApplication
@EnableAutoConfiguration
@Slf4j
public class Main {    
    public static void main(String[] args) {
        try {
            SpringApplication.run(Main.class, args);
        } catch (Exception e) {
            log.error(e.toString(), e);
        }
    }

数据库配置(没有明确声明 bean,EntityManager 自动自动装配):

@Configuration
@ComponentScan("com.phistory.data.dao")
@EntityScan("com.phistory.data.model")
@EnableTransactionManagement
@PersistenceUnit
public class SqlDatabaseConfig {
}

@Transactional
@Repository
public class SqlCarDAOImpl extends SqlDAOImpl<Car, Long> implements SqlCarDAO {

    @Autowired
    public SqlCarDAOImpl(EntityManager entityManager) {
        super(entityManager);
    }

    @Override
    public List<Car> getAll() {
        return super.getEntityManager()
                    .createQuery("FROM Car AS car")
                    .getResultList();
    }
}

父 DAO

@Transactional
@Repository
@Slf4j
@NoArgsConstructor
public abstract class SqlDAOImpl<TYPE extends GenericEntity, IDENTIFIER> implements SqlDAO<TYPE, IDENTIFIER> {

    @Getter
    @PersistenceContext
    private EntityManager entityManager;

    public SqlDAOImpl(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public void saveOrEdit(TYPE entity) {
        if (entity != null) {
            if (entity.getId() == null) {
                log.info("Saving new entity: " + entity.toString());
                this.entityManager.persist(entity);
            } else {
                log.info("Editing entity: " + entity.toString());
                this.entityManager.refresh(entity);
            }
        }
    }

    public void delete(TYPE entity) {
        if (entity != null) {
            log.info("Deleting entity: " + entity.toString());
            this.entityManager.remove(entity);
        }
    }

    public Session getCurrentSession() {
        return this.entityManager.unwrap(Session.class);
    }
}

为什么我加载的实体没有附加到 Session?保存一个新实体显然可以正常工作,因为此时不能管理该实体。

非常感谢 问候

【问题讨论】:

    标签: hibernate jpa spring-boot hql


    【解决方案1】:

    如果您真的想使用 Spring Data JPA,为什么要直接使用 EntityManager?

    您在上面发布的所有内容都是“开箱即用”的:

    应用程序启动器:

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class Main {
    
        public static void main(String[] args) {
            SpringApplication.run(Main.class, args);
        }
    
    }
    

    Spring Data JPA 存储库:

    import org.springframework.data.repository.CrudRepository;
    import org.springframework.stereotype.Repository;
    
    import com.example.model.CollectionParent;
    import java.lang.String;
    import java.util.List;
    
    @Repository
    public interface CarRepository extends CrudRepository<Car, Long> {
         // nearly everything you need comes for free
    }
    

    CarRepository 现在为您提供开箱即用的方法 findAll() : Iterable&lt;Car&gt;delete(Car car)

    DAO / 服务可能如下所示:

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class CarService {
    
        @Autowired
        private CarRepository carRepository;
    
        @Transactional
        public Iterable<Car> findCars() {
            return carRepository.findAll();
        }
    
        @Transactional
        public void updateCar(final Long carId, ...some other params ...) {
            final Car car = carRepository.findOne(carId);
            car.setXXX ...use some other params here ...
            car.setYYY ...use some other params here ...
        }
    
        @Transactional
        public void updateCar(final Car detachedAndModifiedCar) {
             carRepository.save(detachedAndModifiedCar);
        }
    
        ...
    }
    

    如果您将 Car Entity 加载到当前 Persistence Context 中,修改它并让 Hibernate 在刷新时存储更改,则更新很容易实现,最好在数据库事务中。这可以通过使用 @Transactional 注释的 Service / DAO 方法轻松实现。

    也可以将分离的实体传递给服务,只需重新附加并使用carRepository.save(detachedAndModifiedCar) 保存即可。

    即使是您的 saveOrEdit 方法也只是调用 entityManager.refresh(entity) 以防现有实体。这意味着根本没有更新代码,还是我弄错了?实体只会使用数据库中的数据进行更新。

    【讨论】:

    • 高级的东西!我不知道@Repository 可以用来注入这样的 CRUD 逻辑而无需编写它,这实际上是我升级为使用 Spring Boot 的一个较旧的项目,但没有深入研究所有已经出现的新功能这些年来。我会用这种方式重写我所有的 DAO,谢谢!
    • 太棒了。我很高兴能帮到你! :)
    【解决方案2】:

    当你在 dao 之外调用 dao 的方法时,实体是分离的。 要保持会话,应在调用您的 dao 方法的方法上定义 @Transactional 注释。

    例如)

    public class CarService {
    
        @Transactional   <-- {here}
        public void doSomething(){
            Car car = sqlDao.getOne(1);
            sqlDao.update(car);
            sqlDao.delete(car);
        }
    }
    

    【讨论】:

    • 我尝试注释我的控制器和方法,而不是使用 @Transactional 处理实体的版本以及在服务器启动时调用 getAll() 方法的服务,但没有运气,问题仍然存在。我注意到的一个变化是前一个跟踪之后的第二个跟踪:org.springframework.transaction.TransactionSystemException:无法提交 JPA 事务;嵌套异常是 javax.persistence.RollbackException: Transactionmarked as rollbackOnly] with root cause
    猜你喜欢
    • 2020-09-29
    • 1970-01-01
    • 1970-01-01
    • 2020-07-04
    • 1970-01-01
    • 1970-01-01
    • 2014-08-14
    • 2022-11-16
    • 1970-01-01
    相关资源
    最近更新 更多