I recently came across a situation where I have to do some actions when object is deleted through Hibernate session.
I wan't to remove unidirectionl relashonship before an entity is deleted, but the following code results in a stackoverflow exception.
#Component("emsPreListener")
public class IntegrationEntityDeleteListener implements PreDeleteEventListener {
private static final long serialVersionUID = 2245534615822054792L;
#Override
#SuppressWarnings("unchecked")
public boolean onPreDelete(PreDeleteEvent event) {
System.out.println("PRE-DELETE");
Session session = event.getSession();
if (event.getEntity() instanceof Project) {
Transaction transaction = null;
try
{
transaction = session.beginTransaction();
Project project = (Project) event.getEntity();
Criteria criteria = session.createCriteria(ProjectPoll.class);
criteria.add(Restrictions.eq("project", project));
List<ProjectPoll> polls = criteria.list();
if(polls != null) {
for(ProjectPoll projectPoll : polls) {
session.delete(projectPoll);
}
return false;
}
}
catch (Exception exception) {
exception.printStackTrace();
}
finally
{
if(transaction != null) transaction.commit();
}
}
return false;
}
}
This it the only entity with the relashonship.
#Entity
#Table(name = "project_poll")
#PrimaryKeyJoinColumn(name = "poll_id", referencedColumnName = "id")
public class ProjectPoll extends Poll {
private static final long serialVersionUID = -2230614967405436988L;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "project_id")
private Project project;
public Project getProject() {
return project;
}
public void setProject(Project project) {
this.project = project;
}
}
Tnx
You annotated bidirectional relation ProjectPoll.project with cascade=CascadeType.ALL. Removing a projectPoll will remove also a parent (project). This in turn will trigger onPreDelete() once more. Try to remove cascade attribute on bidirectional relation.
Related
I have a ticket object which should be edited. In my ticket object are attributes which reference to object(see below).
...
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "project_id", referencedColumnName="id")
private Project project;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "costCenter_id", referencedColumnName="id")
private CostCenter costCenter;
...
But when I try to update the entity I always get an error:
Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
#PutMapping("/tickets/{id}")
#ResponseStatus(HttpStatus.OK)
public Ticket updateTicket(#RequestBody Ticket ticket) throws Exception{
Optional<Ticket> o = this.ticketRepo.findById(ticket.getId());
o.ifPresent(element -> {
if(ticket.getCostCenter() != null) {
Optional<CostCenter> c = this.costCenterRepo.findById(ticket.getCostCenter().getId());
c.ifPresent( costCenter -> {
element.setCostCenter(costCenter);
});
}
if(ticket.getProject() != null) {
Optional<Project> p = this.projectRepo.findById(ticket.getProject().getId());
p.ifPresent(project -> {
element.setProject(project);
});
}
this.ticketRepo.save(element);
});
return o.orElseThrow(() -> new NotFoundException(ticket.getId()));
}
PS: When I trigger the update without changes everything works fine.
Stacktrace: https://textsaver.flap.tv/lists/2vm5
class AuditorAwareImpl implements AuditorAware<Long> {
#Override
public Optional<Long> getCurrentAuditor() {
PersonRepository personRepo = ApplicationContextProvider.getApplicationContext().getBean(PersonRepository.class);
if(SecurityContextHolder.getContext().getAuthentication() != null) {
Person p = personRepo.findByUserPrincipalName(SecurityContextHolder.getContext().getAuthentication().getName() + "#email.com");
return Optional.of(p.getId());
} else {
Person p = personRepo.findByUserPrincipalName("SYSTEM");
return Optional.of(p.getId());
}
}
}
#Component(value = "applicationContextProvider")
class ApplicationContextProvider implements ApplicationContextAware {
private static class AplicationContextHolder {
private static final InnerContextResource CONTEXT_PROV = new InnerContextResource();
}
private static final class InnerContextResource {
private ApplicationContext context;
private void setContext(ApplicationContext context) {
this.context = context;
}
}
public static ApplicationContext getApplicationContext() {
return AplicationContextHolder.CONTEXT_PROV.context;
}
#Override
public void setApplicationContext(ApplicationContext ac) {
AplicationContextHolder.CONTEXT_PROV.setContext(ac);
}
}
#Data
#Getter
#Setter
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
protected Long id;
#CreatedDate
private Date createdAt;
#CreatedBy
private Long createdBy;
#LastModifiedDate
private Date updatedAt;
#LastModifiedBy
private Long updatedBy;
#PrePersist
protected void prePersist() {
if (this.createdAt == null) createdAt = new Date();
if (this.updatedAt == null) updatedAt = new Date();
}
#PreUpdate
protected void preUpdate() {
this.updatedAt = new Date();
}
#PreRemove
protected void preRemove() {
this.updatedAt = new Date();
}
}
You have a StackOverflowError which strongly suggest you've got some infinite recursion somewhere (or at least a very deep one):
Caused by: java.lang.RuntimeException: java.lang.StackOverflowError
The fact that com.mycompany.test.config.AuditorAwareImpl.getCurrentAuditor shows up repeatedly in your very long stack trace suggests that it is somehow involved in your infinite recursion and I'd wager that it comes from this class somehow triggering whatever triggered it in the first place possibly org.springframework.data.auditing.AuditingHandler. So check your AuditorAwareImpl code and/or the auditing configuration.
Hello, everybody!
Some time ago I run into a trouble: if save method of repository fails, identifier, injected to a bean by Hibernate, remains in the bean. That behaviour may led us to a situation, when we will think about our not persistent bean as about persistent one. I would be pleased to know what practice is common to avoid this situation.
Example test(spring boot + hibernate + oracle database):
#Entity
#SequenceGenerator(name = "TEST_ENTITY_GENERATOR", allocationSize = 1, sequenceName = "TEST_ENTITY_SEQ")
public class TestEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TEST_ENTITY_GENERATOR")
private Long id;
#Column(nullable = false)
private String name;
public Long getId() {
return id;
}
}
#Repository
public interface TestEntityRepository extends JpaRepository<TestEntity, Long> {
}
#RunWith(SpringRunner.class)
#SpringBootTest
public class RemainingIdTest {
#Autowired
private TestEntityRepository testEntityRepository;
#Test
public void test() {
TestEntity entity = new TestEntity();
try {
Assertions.assertThat(entity.getId()).isNull();
testEntityRepository.save(entity);
Assertions.fail("Save must fail");
} catch (DataIntegrityViolationException e) {
Assertions.assertThat(entity.getId()).isNotNull();
}
}
}
A possible solution is to use org.hibernate.event.spi.PreInsertEventListener where we can bind the transaction with a processor that will clear your entity if transaction is failed.
Example:
#Component
public class IdentifierCleaner implements PreInsertEventListener {
#Autowired
private EntityManagerFactory entityManagerFactory;
#PostConstruct
private void init() {
SessionFactoryImpl sessionFactory = entityManagerFactory.unwrap(SessionFactoryImpl.class);
EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
registry.getEventListenerGroup(EventType.PRE_INSERT).appendListener(this);
}
#Override
public boolean onPreInsert(PreInsertEvent event) {
Object entity = event.getEntity();
event.getSession().getActionQueue().registerProcess(((success, session) -> {
if (!success) {
event.getPersister().resetIdentifier(
entity,
event.getId(),
event.getPersister().getVersion(entity),
event.getSession()
);
}
}));
return false;
}
}
I need to map object with an inner object which has one more inner object or inner array.
From this answer
{
"id":1,
"zip_code":"0001",
"user":{
"data":{
"id":1,
"username":"user",
"email":"user#gmail.com"
}
}
}
If I comment object field and left only zipCode and id all working fine.
StackOverflow says SQLite should have 1 open session
So I set hibernate.connection.pool_size to 1, and in this way it throws
Java/Hibernate - Exception: The internal connection pool has reached its maximum size and no connection is currently available
But it was yesterday. Today I again have "locked exception".
I think a problem in cascade. When hibernate try to save a first MainEntity it should save User, but DB already locked and in a result, it throws the exception.
Even if I right I have no idea how to avoid it. I tried to use MERGE cascade type but it doesn't work in my situation.
Add project to a git repo
https://github.com/JoaoMunozIII/hibernate
More info below.
After this sql queries
Hibernate: select mainentity_.mId, mainentity_.mZipCode as mZipCode2_0_ from main_entity mainentity_ where mainentity_.mId=?
Hibernate: select next_val as id_val from hibernate_sequence
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
I got this exception
[SQLITE_BUSY] The database file is locked (database is locked)
org.hibernate.exception.LockAcquisitionException: error performing isolated work
at dialect.SQLiteDialect$3.convert(SQLiteDialect.java:197)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.delegateWork(JdbcIsolationDelegate.java:79)
at org.hibernate.id.enhanced.TableStructure$1.getNextValue(TableStructure.java:125)
at org.hibernate.id.enhanced.NoopOptimizer.generate(NoopOptimizer.java:40)
at org.hibernate.id.enhanced.SequenceStyleGenerator.generate(SequenceStyleGenerator.java:412)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:105)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:97)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:651)
at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:643)
at org.hibernate.engine.spi.CascadingActions$5.cascade(CascadingActions.java:218)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:391)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:316)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:155)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:104)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:252)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:97)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:651)
at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:643)
at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:638)
at DAO.saveEntityDb(DAO.java:16)
at Main.main(Main.java:32)
Caused by: org.sqlite.SQLiteException: [SQLITE_BUSY] The database file is locked (database is locked)
at org.sqlite.core.DB.newSQLException(DB.java:909)
at org.sqlite.core.DB.newSQLException(DB.java:921)
at org.sqlite.core.DB.throwex(DB.java:886)
at org.sqlite.core.DB.exec(DB.java:155)
at org.sqlite.jdbc3.JDBC3Connection.commit(JDBC3Connection.java:174)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.delegateWork(JdbcIsolationDelegate.java:60)
In the project.
I create models:
Main Entity class
#Entity
#Table(name = "main_entity")
public class MainEntityModel {
#Id
private Long mId;
private Long mZipCode;
#OneToOne(cascade = CascadeType.ALL, targetEntity = User.class)
#JoinColumn(name = "id", insertable = false, updatable = false)
private User mUser;
public Long getmId() {
return mId;
}
public void setmId(Long mId) {
this.mId = mId;
}
public Long getmZipCode() {
return mZipCode;
}
public void setmZipCode(Long mZipCode) {
this.mZipCode = mZipCode;
}
public User getmUser() {
return mUser;
}
public void setmUser(User mUser) {
this.mUser = mUser;
}
}
User class
#Entity
#Table(name = "user_data")
public class User {
#OneToOne(cascade = CascadeType.ALL, targetEntity = UserEntity.class)
#JoinColumn(name="mId")
private UserEntity mData;
public UserEntity getData() {
return mData;
}
public void setmData(UserEntity mData) {
this.mData = mData;
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
public User() {
}
}
User entity class
#Entity
#Table(name = "user_entity")
public class UserEntity {
#Id
private Long mId;
private String mUsername;
public Long getmId() {
return mId;
}
public void setmId(Long mId) {
this.mId = mId;
}
public String getmUsername() {
return mUsername;
}
public void setmUsername(String mUsername) {
this.mUsername = mUsername;
}
}
Hibernate Util
public class Hibernate {
private static SessionFactory sessionFactory = null;
private static String dbPath = "D:" + File.separator + "temp.db";
public static SessionFactory getSessionFactory() {
System.out.println("factory " + sessionFactory);
if (sessionFactory == null) {
Configuration cfg = new Configuration()
.setProperty("hibernate.connection.driver_class", "org.sqlite.JDBC")
.setProperty("hibernate.dialect", "dialect.SQLiteDialect")
.setProperty("hibernate.connection.pool_size", "1")
.setProperty("hibernate.connection.url", "jdbc:sqlite:" + dbPath)
.setProperty("hibernate.connection.username", "pass")
.setProperty("hibernate.connection.password", "pass")
.setProperty("hibernate.show_sql", "true")
.setProperty("hibernate.format_sql", "false")
.setProperty("hibernate.hbm2ddl.auto", "create-drop")
.setProperty("hibernate.use_sql_comments", "false")
.addAnnotatedClass(MainEntityModel.class)
.addAnnotatedClass(User.class)
.addAnnotatedClass(UserEntity.class);
sessionFactory = cfg.buildSessionFactory();
return sessionFactory;
} else {
return sessionFactory;
}
}
And Main class
public class Main {
static Long userId = 1l;
private static Long entityId = 1l;
private static Long entityZip = 1000l;
private static List<MainEntityModel> mainEntityModelList = new ArrayList();
public static void main(String[] args) {
UserEntity userEntity = new UserEntity();
userEntity.setmId(userId);
userEntity.setmUsername("User Name " + userId++);
User user = new User();
user.setmData(userEntity);
for (int i = 0; i < 5; i++) {
MainEntityModel mainEntityModel = new MainEntityModel();
mainEntityModel.setmId(entityId++);
mainEntityModel.setmUser(user);
mainEntityModel.setmZipCode(entityZip++);
mainEntityModelList.add(mainEntityModel);
}
DAO.saveEntityDb(mainEntityModelList);
System.out.println("saved");
List<MainEntityModel> savedList = DAO.getEntityDb();
for (MainEntityModel entity: savedList) {
System.out.println(entity.getmZipCode() + "\t"
+ entity.getmUser().getData().getmUsername()
);
}
}
}
DAO class
public class DAO {
static final Session session = Hibernate.getSessionFactory().openSession();
public static void saveEntityDb(List<MainEntityModel> entityList){
Transaction tx=null;
try {
tx = session.beginTransaction();
for (MainEntityModel entity : entityList) {
session.saveOrUpdate(entity);
}
session.flush();
tx.commit();
} catch (Exception ex) {
ex.printStackTrace();
tx.rollback();
} finally{
if(session != null) {
session.close();
}
}
}
public static List<MainEntityModel> getEntityDb(){
Session session = Hibernate.getSessionFactory().openSession();
List<MainEntityModel> entityModel = session.createQuery("from MainEntityModel").list();
session.close();
return entityModel;
}
}
Problem: The auto generation of user.id - it mutually locks/concurs with the insert statement.
Problem: After the lock is fixed, we run into NPE's in the output loop
Solution:
Problem
Manually assign user.id and remove #GeneratedValue() annotation. (verified/tested)
Main.java://or somewhere else
...
user.setId(userId);
...
User.java:
...
#Id
//!#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
...
A nice alternative for "somewhere else" is:
User.java:
...
public void setmData(UserEntity mData) {
this.mData = mData;
if(mData == null) {
this.id = null;
} else {
this.id = mData.getmId();
}
}
#Id
//!#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
...
Or: Perist User/UserData in prior/separate transaction. ...like...
"improve" your DAO.java:
public static <T extends Object> void saveEntityDb(T... entityList) {
//local variable!
final Session session = Hibernate.getSessionFactory().openSession();
Transaction tx = null;
try {
if (session.isConnected()) {
tx = session.beginTransaction();
for (T entity : entityList) {
session.saveOrUpdate(entity);
}
session.flush();
tx.commit();
}
} catch (HibernateException ex) {
if (tx != null && tx.getStatus().canRollback()) {
tx.rollback();
}
} finally {
if (session != null) {
session.close();
}
}
}
and use it twice(!):
Main.java:
...
User user = new User();
user.setmData(userEntity);
//do this before...
DAO.saveEntityDb(user);
List<MainEntityModel> mainEntityModelList = ...
//...you do this
DAO.saveEntityDb(mainEntityModelList.toArray(new MainEntityModel[0]));
Problem
get rid of the insertable = false, updatable = false on MainEntityModel's JoinColumn.
...the OneToOne is somewhat strange here (#MainEntityModel) , it works, but rather used as a ManyToOne (5 to 1 ?!)
I try to understand the purpose of #ShallowReference by creating a test case but I don't see any differences in the Javers changes. My application is a Spring Boot application with repository annotation.
I have a one to many bidirectional relationship between Customer and Project. The test case creates a Customer and a Project, then the project is added to the customer. I tried to run the test case with and without the #ShallowReference on the Set<Project> field to see the #ShallowReference behaviour. Then I query the changes but they are the same in both cases. I am looking for a test case to understand the #ShallowReference.
Test case:
#RunWith(SpringRunner.class)
#DataJpaTest
#Import({JaversSqlAutoConfiguration.class})
public class AuditTest {
#Autowired
private ProjectRepository projectRepository;
#Autowired
private Javers javers;
#Autowired
private CustomerRepository customerRepository;
#Test
public void testJavers() {
Customer customer = new Customer();
customer.setName("Stackoverflow");
customerRepository.save(customer);
Project project = new Project();
project.setName("Framework");
customer.addProject(project);
projectRepository.save(project);
QueryBuilder jqlQuery = QueryBuilder.anyDomainObject().withNewObjectChanges();
List<Change> changes = javers.findChanges(jqlQuery.build());
assertEquals(9, changes.size());
project.setName("Backend Framework");
projectRepository.save(project);
changes = javers.findChanges(jqlQuery.build());
assertEquals(10, changes.size());
}
}
Customer entity:
#Entity
#Table(name = "CUSTOMER")
#SQLDelete(sql = "UPDATE customer SET deleted = true WHERE id = ?")
#Where(clause = "deleted = false")
public class Customer extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String name;
#OneToMany(mappedBy = "customer")
// #ShallowReference
private Set<Project> projects = new HashSet<>();
// Getter/Setter
public void addProject(Project project) {
projects.add(project);
project.setCustomer(this);
}
public void removeProject(Project project) {
projects.remove(project);
project.setCustomer(null);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Customer customer = (Customer) o;
return Objects.equals(id, customer.id);
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
Project entity:
#Entity
#Table(name = "PROJECT")
#SQLDelete(sql = "UPDATE project SET deleted = true WHERE id = ?")
#Where(clause = "deleted = false")
#EntityListeners(AuditingEntityListener.class)
public class Project extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.EAGER)
private Customer customer;
// Getter/Setter
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Project)) return false;
Project project = (Project) o;
return Objects.equals(id, project.id);
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
#ShallowReference doesn't work for collections. We have an open issue for that https://github.com/javers/javers/issues/528
PR's are welcome.
Let's say I have entity A and entity B. Entity A have #OneTomany relationship with B.
I want to persist row in A only if it has one or more child object's associated with it else throw an exception.
How can i achieve the above requirement in hibernate
You neglected to mention what version of Hibernate you are using. In any case, this falls within the purview of validation. Luckily, in both Hibernate 3 and 4 you can utilize Hibernate Validator to do the job:
public class EntityB implements Serializable {
}
public class EntityA implements Serializable {
#NotNull
#Size(min = 1)
private Set<EntityB> relatedEntities;
}
You may need to pull in the Hibernate Validator jars into your project in order to be able to do this.
Entity class:Register.class
public class Register{
private Long regId;
#OneToMany(mappedBy = "reg")
private Set addrSet;
public Set getAddrSet() {
return addrSet;
}
public void setAddrSet(Set<Address> addrSet) {
this.addrSet = addrSet;
}
}
Entity Class:Address.java
public class Address{
object values;
#ManyToOne
private Register reg;
public Register getReg() {
return reg;
}
public void setReg(Register reg) {
this.reg = reg;
}
}
public void class searchObject(){
public List lst;
public register searchRegisterRow(Long regId){
Session session = null;
SessionFactory sessionFactory = null;
register result = null;
try{
sessionFactory = new Configuration().configure().buildSessionFactory();
session =sessionFactory.openSession();
String SQL_QUERY ="from Register r where r.redId = "+regId;
Register reg = session.createQuery(SQL_QUERY);
for(Iterator it=lst.iterator();it.hasNext();){
reg=(Register)it.next();
if(reg.getAddrSet().size() > 0){
result = reg;
}
else{
throw new Exception();
}
}
return result;
}
}catch(Exception e){
System.out.println(e.getMessage());
}finally{
// Actual contact insertion will happen at this step
session.flush();
session.close();
}
}
}
I think you should try above code. this will help you.