Spring Boot JPA EntityListener query causes "don't flush the Session after an exception occurs" - spring-boot

Problem:
I create object A with an EntityListener with #PostPersist-method that will create object B, this works like a charm!
I need to introduce some logic before creating object B, I need to query the database and see if a similar B object already exists in the database. But when I run my query
#Query("select case when count(n) > 0 then true else false end from Notification n where student = :student and initiator = :initiator and entityType = :entityType and entityId = :entityId")
boolean alreadyNotified(#Param("student") Student student, #Param("initiator") Student initiator, #Param("entityType") EntityType entityType, #Param("entityId") Long entityId);
I get the following error:
ERROR org.hibernate.AssertionFailure.<init>(31) - HHH000099: an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: null id in se.hitract.model.Likes entry (don't flush the Session after an exception occurs)
org.hibernate.AssertionFailure: null id in se.hitract.model.Likes entry (don't flush the Session after an exception occurs)
Background:
I have a Spring Boot project with Hibernate and MySql DB and I'm building a simple social media platform where students can upload posts/images and other user can like/comments.
When someone like/comment an object a notification should be sent to the other user. The like object:
#SuppressWarnings("serial")
#Entity
#Table(uniqueConstraints=#UniqueConstraint(columnNames = {"entityType", "entityId", "studentId"}))
#EntityListeners(LikeListener.class)
public class Likes extends CommonEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long likeId;
#NotNull
#Enumerated(EnumType.STRING)
private EntityType entityType;
private Long entityId;
...
}
The LikeListener:
#Component
public class LikeListener {
#PostPersist
public void doThis(Likes like) {
NotificationService notificationService = BeanUtil.getBean(NotificationService.class);
if(like.getEntityType().equals(EntityType.INSPIRATION)) {
InspirationService inspirationService = BeanUtil.getBean(InspirationService.class);
Inspiration inspiration = inspirationService.get(like.getEntityId());
notificationService.createLikeNotification(inspiration.getStudent(), like.getStudent(), EntityType.INSPIRATION, inspiration.getId());
}
if(like.getEntityType().equals(EntityType.COMMENT)) {
CommentService commentService = BeanUtil.getBean(CommentService.class);
Comment comment = commentService.get(like.getEntityId());
notificationService.createLikeNotification(comment.getStudent(), like.getStudent(), EntityType.COMMENT, comment.getId());
}
}
}
and the problem:
public Notification createLikeNotification(Student student, Student initiator, EntityType entityType, Long entityId) {
if(student.equals(initiator) || alreadyNotified(student, initiator, entityType, entityId)) {
return null;
}
Notification notification = createNotification(student,
initiator,
NOTIFICATION_TYPE.LIKE,
entityType,
entityId,
null);
return repository.save(notification);
}
public boolean alreadyNotified(Student student, Student initiator, EntityType entityType, Long entityId) {
return repository.alreadyNotified(student, initiator, entityType, entityId);
}
If I remove the alreadyNotified-call no error is thrown. What am I missing?
It seems that Hibernate flushes the Likes-save before my query is run but then it fails. Do I need to do some manual flush/refresh? I think Hibernate should solve this for me.

Related

Saving Entity with Cached object in it causing Detached Entity Exception

I'm trying to save an Entity in DB using Spring Data/Crud Repository(.save) that has in it another entity that was loaded through a #Cache method. In other words, I am trying to save an Ad Entity that has Attributes entities in it, and those attributes were loaded using Spring #Cache.
Because of that, I'm having a Detached Entity Passed to Persist Exception.
My question is, is there a way to save the entity still using #Cache for the Attributes?
I looked that up but couldn't find any people doing the same, specially knowing that I am using CrudRepository that has only the method .save(), that as far as I know manages Persist, Update, Merge, etc.
Any help is very much appreciated.
Thanks in advance.
Ad.java
#Entity
#DynamicInsert
#DynamicUpdate
#Table(name = "ad")
public class Ad implements SearchableAdDefinition {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private User user;
#OneToMany(mappedBy = "ad", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<AdAttribute> adAttributes;
(.....) }
AdAttribute.java
#Entity
#Table(name = "attrib_ad")
#IdClass(CompositeAdAttributePk.class)
public class AdAttribute {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ad_id")
private Ad ad;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "attrib_id")
private Attribute attribute;
#Column(name = "value", length = 75)
private String value;
public Ad getAd() {
return ad;
}
public void setAd(Ad ad) {
this.ad = ad;
}
public Attribute getAttribute() {
return attribute;
}
public void setAttribute(Attribute attribute) {
this.attribute = attribute;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#Embeddable
class CompositeAdAttributePk implements Serializable {
private Ad ad;
private Attribute attribute;
public CompositeAdAttributePk() {
}
public CompositeAdAttributePk(Ad ad, Attribute attribute) {
this.ad = ad;
this.attribute = attribute;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CompositeAdAttributePk compositeAdAttributePk = (CompositeAdAttributePk) o;
return ad.getId().equals(compositeAdAttributePk.ad.getId()) && attribute.getId().equals(compositeAdAttributePk.attribute.getId());
}
#Override
public int hashCode() {
return Objects.hash(ad.getId(), attribute.getId());
}
}
Method using to load Attributes:
#Cacheable(value = "requiredAttributePerCategory", key = "#category.id")
public List<CategoryAttribute> findRequiredCategoryAttributesByCategory(Category category) {
return categoryAttributeRepository.findCategoryAttributesByCategoryAndAttribute_Required(category, 1);
}
Method used to create/persist the Ad:
#Transactional
public Ad create(String title, User user, Category category, AdStatus status, String description, String url, Double price, AdPriceType priceType, Integer photoCount, Double minimumBid, Integer options, Importer importer, Set<AdAttribute> adAtributes) {
//Assert.notNull(title, "Ad title must not be null");
Ad ad = adCreationService.createAd(title, user, category, status, description, url, price, priceType, photoCount, minimumBid, options, importer, adAtributes);
for (AdAttribute adAttribute : ad.getAdAttributes()) {
adAttribute.setAd(ad);
/* If I add this here, I don't face any exception, but then I don't take benefit from using cache:
Attribute attribute = attributeRepository.findById(adAttribute.getAttribute().getId()).get();
adAttribute.setAttribute(attribute);
*/
}
ad = adRepository.save(ad);
solrAdDocumentRepository.save(AdDocument.adDocumentBuilder(ad));
return ad;
}
I don't know if you still require this answer or not, since it's a long time, you asked this question. Yet i am going to leave my comments here, someone else might get help from it.
Lets assume, You called your findRequiredCategoryAttributesByCategory method, from other part of your application. Spring will first check at cache, and will find nothing. Then it will try to fetch it from Database. So it will create an hibernate session, open a transaction, fetch the data, close the transaction and session. Finally after returning from the function, it will store the result set in cache for future use.
You have to keep in mind, those values, currently in the cache, they are fetched using a hibernate session, which is now closed. So they are not related to any session, and now at detached state.
Now, you are trying to save and Ad entity. For this, spring created a new hibernate session, and Ad entity is attached to this particular session. But the attributes object, that you fetched from the Cache are detached. That's why, while you are trying to persist Ad entity, you are getting Detached Entity Exception
To resolve this issue, you need to re attach those objects to current hibernate session.I use merge() method to do so.
From hibernate documentation here https://docs.jboss.org/hibernate/orm/3.5/javadocs/org/hibernate/Session.html
Copy the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. Return the persistent instance. If the given instance is unsaved, save a copy of and return it as a newly persistent instance. The given instance does not become associated with the session. This operation cascades to associated instances if the association is mapped with cascade="merge".
Simply put, this will attach your object to hibernate session.
What you should do, after calling your findRequiredCategoryAttributesByCategory method, write something like
List attributesFromCache = someService.findRequiredCategoryAttributesByCategory();
List attributesAttached = entityManager.merge( attributesFromCache );
Now set attributesAttached to your Ad object. This won't throw exception as attributes list is now part of current Hibernate session.

How to write a RestController to update a JPA entity from an XML request, the Spring Data JPA way?

I have a database with one table named person:
id | first_name | last_name | date_of_birth
----|------------|-----------|---------------
1 | Tin | Tin | 2000-10-10
There's a JPA entity named Person that maps to this table:
#Entity
#XmlRootElement(name = "person")
#XmlAccessorType(NONE)
public class Person {
#Id
#GeneratedValue
private Long id;
#XmlAttribute(name = "id")
private Long externalId;
#XmlAttribute(name = "first-name")
private String firstName;
#XmlAttribute(name = "last-name")
private String lastName;
#XmlAttribute(name = "dob")
private String dateOfBirth;
// setters and getters
}
The entity is also annotated with JAXB annotations to allow XML payload in
HTTP requests to be mapped to instances of the entity.
I want to implement an endpoint for retrieving and updating an entity with a given id.
According to this answer to a similar question,
all I need to do is to implement the handler method as follows:
#RestController
#RequestMapping(
path = "/persons",
consumes = APPLICATION_XML_VALUE,
produces = APPLICATION_XML_VALUE
)
public class PersonController {
private final PersonRepository personRepository;
#Autowired
public PersonController(final PersonRepository personRepository) {
this.personRepository = personRepository;
}
#PutMapping(value = "/{person}")
public Person savePerson(#ModelAttribute Person person) {
return personRepository.save(person);
}
}
However this is not working as expected as can be verified by the following failing test case:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
public class PersonControllerTest {
#Autowired
private TestRestTemplate restTemplate;
private HttpHeaders headers;
#Before
public void before() {
headers = new HttpHeaders();
headers.setContentType(APPLICATION_XML);
}
// Test fails
#Test
#DirtiesContext
public void testSavePerson() {
final HttpEntity<Object> request = new HttpEntity<>("<person first-name=\"Tin Tin\" last-name=\"Herge\" dob=\"1907-05-22\"></person>", headers);
final ResponseEntity<Person> response = restTemplate.exchange("/persons/1", PUT, request, Person.class, "1");
assertThat(response.getStatusCode(), equalTo(OK));
final Person body = response.getBody();
assertThat(body.getFirstName(), equalTo("Tin Tin")); // Fails
assertThat(body.getLastName(), equalTo("Herge"));
assertThat(body.getDateOfBirth(), equalTo("1907-05-22"));
}
}
The first assertion fails with:
java.lang.AssertionError:
Expected: "Tin Tin"
but: was "Tin"
Expected :Tin Tin
Actual :Tin
In other words:
No server-side exceptions occur (status code is 200)
Spring successfully loads the Person instance with id=1
But its properties do not get updated
Any ideas what am I missing here?
Note 1
The solution provided here is not working.
Note 2
Full working code that demonstrates the problem is provided
here.
More Details
Expected behavior:
Load the Person instance with id=1
Populate the properties of the loaded person entity with the XML payload using Jaxb2RootElementHttpMessageConverter or MappingJackson2XmlHttpMessageConverter
Hand it to the controller's action handler as its person argument
Actual behavior:
The Person instance with id=1 is loaded
The instance's properties are not updated to match the XML in the request payload
Properties of the person instance handed to the controller's action handler method are not updated
this '#PutMapping(value = "/{person}")' brings some magic, because {person} in your case is just '1', but it happens to load it from database and put to ModelAttribute in controller. Whatever you change in test ( it can be even empty) spring will load person from database ( effectively ignoring your input ), you can stop with debugger at the very first line of controller to verify it.
You can work with it this way:
#PutMapping(value = "/{id}")
public Person savePerson(#RequestBody Person person, #PathVariable("id") Long id ) {
Person found = personRepository.findOne(id);
//merge 'found' from database with send person, or just send it with id
//Person merged..
return personRepository.save(merged);
}
wrong mapping in controller
to update entity you need to get it in persisted (managed) state first, then copy desired state on it.
consider introducing DTO for your bussiness objects, as, later, responding with persisted state entities could cause troubles (e.g. undesired lazy collections fetching or entities relations serialization to XML, JSON could cause stackoverflow due to infinite method calls)
Below is simple case of fixing your test:
#PutMapping(value = "/{id}")
public Person savePerson(#PathVariable Long id, #RequestBody Person person) {
Person persisted = personRepository.findOne(id);
if (persisted != null) {
persisted.setFirstName(person.getFirstName());
persisted.setLastName(person.getLastName());
persisted.setDateOfBirth(person.getDateOfBirth());
return persisted;
} else {
return personRepository.save(person);
}
}
Update
#PutMapping(value = "/{person}")
public Person savePerson(#ModelAttribute Person person, #RequestBody Person req) {
person.setFirstName(req.getFirstName());
person.setLastName(req.getLastName());
person.setDateOfBirth(req.getDateOfBirth());
return person;
}
The issue is that when you call personRepository.save(person) your person entity does not have the primary key field(id) and so the database ends up having two records with the new records primary key being generated by the db. The fix will be to create a setter for your id field and use it to set the entity's id before saving it:
#PutMapping(value = "/{id}")
public Person savePerson(#RequestBody Person person, #PathVariable("id") Long id) {
person.setId(id);
return personRepository.save(person);
}
Also, like has been suggested by #freakman you should use #RequestBody to capture the raw json/xml and transform it to a domain model. Also, if you don't want to create a setter for your primary key field, another option may be to support an update operation based on any other unique field (like externalId) and call that instead.
For updating any entity the load and save must be in same Transaction,else it will create new one on save() call,or will throw duplicate primary key constraint violation Exception.
To update any we need to put entity ,load()/find() and save() in same transaction, or write JPQL UPDATE query in #Repository class,and annotate that method with #Modifying .
#Modifying annotation will not fire additional select query to load entity object to update it,rather presumes that there must be a record in DB with input pk,which needs to update.

JPA Hibernate Spring Repository ensures transaction completes on save?

I am creating a simple spring application which is supposed to book seats in a seminar. Lets say Booking class looks like this
#Entity
#Table(name = "bookings")
#IdClass(BookingId.class)
public class Booking{
#Id
private Long seminarId;
#Id
private String seatNo;
// .. other fields like perticipant info
// .. getter setters
}
of course the BookingId class:
public class BookingId implements Serializable{
private static final long serialVersionUID = 1L;
private Long seminarId;
private String seatNo;
// .. constructors, getters, setters
}
And I have a repository
#Repository
public interface BookingsRepository extends JpaRepository<Booking, BookingId>{
}
in the controller when a booking request arrives I first check if a booking with same seminer id and seat number already exists, if it doesn't exist I create one
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<BaseCrudResponse> createNewBooking(#Valid #RequestBody NewBookingDao newBookingDao, BindingResult bindingResult){
logger.debug("Request for a new booking");
// .. some other stuffs
Booking newBooking = new Booking();
newBooking.setSeminarId(newBookingDao.getSeminarId());
newBooking.setSeatNumber(newBookingDao.getSeatNumber());
// .. set other fields
Booking existing = bookingsRepository.findOne(new BookingId(newBooking.getSeminarId(), newBooking.getSeatNumber());
if (existing == null)
bookingsRepository.save(newBooking);
return new ResponseEntity<>(new BaseCrudResponse(0), HttpStatus.CREATED);
}
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
Now what will happen if the save method of the repository didn't finish commiting transaction and another request already gets past the existence check ? There might be incorrect booking (the last commit will override the previous). Is this scenario likely to happen ? Will the repository ensures that it completes the transaction before another save call ?
Also is there any way to tell Jpa to throw some exception (for IntegrityConstraintException if the composite key (in this case seminerId and seatNumber) already exists ? Now in the present setting its just updating the row.
You can use javax.persistence.LockModeType.PESSIMISTIC_WRITE so other transactions except the one that got the lock cannot update the entity.
If you use spring-data > 1.6 you can annotate the repository method with #Lock :
interface BookingsRepository extends Repository<Booking, Long> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
Booking findOne(Long id);
}
For sure you need to handle the locking exception that may be thron in the controller.

Variable 'this.userInfo' is unbound and cannot be determined

I am developing a maven JDO project, but I am getting this error when I am trying to make relation between two tables (user_login, user_role)
User_Login: user_id(primary key), user_name, user_password,user_role_id
User_Role: id(primary key), role
user_role_id is same as id of user_role table
User.java:
#PersistenceCapable(table = "user_login")
public class User {
#PrimaryKey
#Column(name="user_id")
private Integer userId=0;
#Column(name="user_profile_name")
private String userProfileName=null;
#Column(name="user_email")
private String userEmail=null;
#Column(name="user_contact")
private String userContact=null;
#Column(name="user_name")
private String userName=null;
#Column(name="user_password")
private String userPassword=null;
#ManyToOne
#Column(name="user_role_id")
private Integer userRoleId=0;
Role.java:
#PersistenceCapable(table = "user_role")
public class Role {
#PrimaryKey
#Column(name="id")
private Integer id=0;
#Column(name="role")
private String role=null;
#OneToMany
private User userInfo=null;
DAOImpol:
public List<Role> getUser(String username, String userpassword) {
PersistenceManager pm = this.pmf.getPersistenceManager();
Transaction tx = pm.currentTransaction();
JDOPersistenceManager jdopm = (JDOPersistenceManager)pm;
try {
// Start the transaction
tx.begin();
TypesafeQuery<User> tq = jdopm.newTypesafeQuery(User.class);
//QUser user = QUser.candidate();
QRole role = QRole.candidate();
QUser userInfo=role.userInfo;
List<Role> result = tq.filter(userInfo.userName.eq(username).and(userInfo.userPassword.eq(userpassword))).executeList();
//result = tq.executeResultList(true, user.userId);
if(result.size()>0){
log.info(">>>>>00000000"+" "+result.get(0).getUser().getUserEmail());
log.info(">>>>>11111111"+" "+result.get(0).getRoleId()+" "+result.get(0).getRole());
}else{
log.info("<<<<<<<=====000000");
}
// Commit the transaction, flushing the object to the datastore
tx.commit();
return result;
}
finally {
if (tx.isActive())
{
// Error occurred so rollback the transaction
tx.rollback();
}
pm.close();
}
I am getting this error:
javax.jdo.JDOUserException: Variable 'this.userInfo' is unbound and
cannot be determined (is it a misspelled field name? or is not intended
to be a variable?)
NestedThrowables:
org.datanucleus.exceptions.NucleusUserException: Variable
'this.userInfo' is unbound and cannot be determined (is it a
misspelled
field name? or is not intended to be a variable?)
I found that you'll get this error from JDO if you're using progaurd and progaurd renames your private fields. Adding a -keep to the progaurd config to keep the package with your Persistence Capable classes will fix it.
For example, if you keep all of your Persistence Capable classes in com.example.server.orm package you'd add this to progaurd.conf
-keep class com.example.server.orm.** {*;}

Using oneToMany relation, but saving data in individual tables at different point of time

I am working on a Spring-MVC application which has 2 tables in database and 2 domain classes. Class Person has oneTOMany relation with class Notes. I would like to add Person and notes both in database. So I googled, to find out many MVC based examples for the same problem. However they seem to assume a few things :
Data is being added in a static manner by the developer, mostly through Static void main() or another class.
Data regarding all the classes which are related is added altogether, eg : Table A has oneToMany relation, so the code will add data for both the tables in one class or one jsp file.
Other frameworks like Spring-Security at play(This point is understood).
So basically, similar examples with different names and developers is what I found. My problem is :
I don't have static void main, don't intend to use it.
I am adding data through HTML page wrapped inside JSP page.
I or the user will first register through the register form, just login later and then add notes, so I am not adding data for both tables at same time. (I have to believe this is possible by Hibernate)
Error :
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.journaldev.spring.model.Person
org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:294)
org.hibernate.type.EntityType.getIdentifier(EntityType.java:537)
org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:311)
org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:321)
org.hibernate.type.TypeHelper.findDirty(TypeHelper.java:294)
Person Model :
#Entity
#Table(name="person")
public class Person implements UserDetails{
private static final GrantedAuthority USER_AUTH = new SimpleGrantedAuthority("ROLE_USER");
#Id
#Column(name="id")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "person_seq_gen")
#SequenceGenerator(name = "person_seq_gen",sequenceName = "person_seq")
private int id;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "person1")
private Set<Notes> notes1;
public Set<Notes> getNotes1() {
return notes1;
}
public void setNotes1(Set<Notes> notes1) {
this.notes1 = notes1;
}
Notes model :
#Entity
#Table(name="note")
public class Notes {
#Id
#Column(name="noteid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "note_gen")
#SequenceGenerator(name = "note_gen",sequenceName = "note_seq")
private int noteId;
#ManyToOne
#JoinColumn(name = "id")
private Person person1;
public Person getPerson1() {
return person1;
}
public void setPerson1(Person person1) {
this.person1 = person1;
}
NotesDAOImpl :
#Transactional
#Repository
public class NotesDAOImpl implements NotesDAO{
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sf){
this.sessionFactory = sf;
}
#Override
public void addNote(Notes notes, int id) {
Session session = this.sessionFactory.getCurrentSession();
session.save(notes);
}
SQL schema :
CREATE TABLE public.person (
id INTEGER NOT NULL,
firstname VARCHAR,
username VARCHAR,
password VARCHAR,
CONSTRAINT personid PRIMARY KEY (id)
);
CREATE TABLE public.note (
noteid INTEGER NOT NULL,
sectionid INTEGER,
canvasid INTEGER,
text VARCHAR,
notecolor VARCHAR,
noteheadline VARCHAR,
id INTEGER NOT NULL,
CONSTRAINT noteid PRIMARY KEY (noteid)
);
ALTER TABLE public.note ADD CONSTRAINT user_note_fk
FOREIGN KEY (id)
REFERENCES public.person (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION
NOT DEFERRABLE;
Btw, the id in addNote method is just me checking if SpringSecurity is actually sending userid, and has properly loggedin, debug purpose.
So, I am unable to add notes once user is logged in, what am I doing wrong? Or this is not possible with Hibernate. In that case, let me find a gun to shoot myself.. :P
Your code will try to save notes. But these notes will not be linked to any Person. You have to do below sequence of operation.
Find the logged in person or the person for which you want to save the notes.
Create notes object which will be in transient state.
Attach notes to the person.
If it is bidirectional relationaship, then person to notes.
Below is the code template.
#Transactional
#Repository
public class NotesDAOImpl implements NotesDAO{
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sf){
this.sessionFactory = sf;
}
#Override
public void addNote(Notes notes, int id) {
Session session = this.sessionFactory.getCurrentSession();
Person person = getPerson(); // this method should get logged in person or the person for whom you want to save the notes.
if (person.getNotes() == null) {
Set<Note> notes = new HashSet<Note>();
person.setNotes(notes);
}
person.getNotes().add(note);
note.setPerson(person); // If bidirectional relationship.
session.update(person); // if update does not work, try merge();
}
Also make sure you have cascade type set to MERGE in person entity on notes field.
Note: Above code is just example from your code and may have some compilation error. please correct according to your requirement.

Resources