I have a project that uses Spring Boot JPA (spring-boot-starter-data-jpa dependency) which uses Hibernate as a JPA implementation.
I have my container auto configured (@EnableAutoConfiguration) and I use an EntityManager for CRUD operations.
Issue: I load my entities at startup via HQL queries using this EntityManager, but when I want to edit or delete any of them I get the following error
org.springframework.dao.InvalidDataAccessApiUsageException: Removing a detached instance com.phistory.data.model.car.Car#2; nested exception is java.lang.IllegalArgumentException: Removing a detached instance com.phistory.data.model.car.Car#2
org.springframework.dao.InvalidDataAccessApiUsageException: Entity not managed; nested exception is java.lang.IllegalArgumentException: Entity not managed
Libraries:
Main:
@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);
}
}
Database config (no beans explicitly declared, EntityManager gets automatically autowired):
@Configuration
@ComponentScan("com.phistory.data.dao")
@EntityScan("com.phistory.data.model")
@EnableTransactionManagement
@PersistenceUnit
public class SqlDatabaseConfig {
}
DAO
@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();
}
}
Parent 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);
}
}
Why are the entities I load not attached to the Session? Saving a new entity obviously works fine since the entity must not be managed at that point.
Thanks a lot Greetings
Why are you using the EntityManager directly if you actually want to use Spring Data JPA?
Everything you posted above is available "out of the box":
Application Starter:
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 Repository:
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
}
The CarRepository now offers you the methods findAll() : Iterable<Car>
and delete(Car car)
out of the box.
The DAO / Service could look like this:
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);
}
...
}
The update is simply realized if you load an Car Entity into the current Persistence Context, modify it and let Hibernate store the changes on flush time, preferable within a database transaction. That can be easily realized with a Service / DAO method annotated with @Transactional
.
A detached Entity can also be passed to the Service, just reattach and save it with carRepository.save(detachedAndModifiedCar)
.
Even your saveOrEdit method is just calling entityManager.refresh(entity)
in case of an existing entity. That means there is no updating code at all, or did I get it wrong? The Entity will just be updated with the data from the database.