One to many Spring Result Mapping - java

This is my first time posting problem hope someone can help me on this mapping the result into:
List<Clients>
So here's the sample result from database:
NameID | Name | Notes | Date
1 | Client1 | Get this on monday. | null
1 | Client1 | Get this on wednesday. | null
2 | Client2 | Meet on saturday. | null
So here's my mode (java class).
Name.java
private int NameId;
private String ClientName;
.. getter and setter
Notes.java
private int NotesId;
private String ClientNote;
.. getter and setter
Clients.java
private Name name;
private List<Notes> notes;
.. getter and setter
ClientResultSet.java
class ClientResultSet implements ResultSetExtractor<List<Clients>>{
#Override
public List<Clients> extractData(ResultSet rs) throws SQLException, DataAccessException {
Map<int, Clients> map = new HashMap<int, Clients>();
while(rs.next()) {
int NameId= rs.getInt("NameId");
Clients clients= map.get(contactId);
if (clients== null) {
// I'm losing my thoughts here. :'(
}
}
return null;
}
}
Result I want to achieve:
[
{
name:{
NameId: 1,
Name: "Client1"
},
notes[
{
NotesId: 1,
ClientNote: "Get this on monday",
Date: null
},
{
NoteId: 2,
ClientNote: "Get this on wednesday",
Date: null
}
]
},
{
name:{},
notes:[{},{}]
}
... and so on
]
I'm reading ResultSetExtractor but I don't know how to implement it. Thanks in advance and have a good day.

This is how your database should look like, you will create 4 tables, client, name, note, client_note. the client_note table should have the client id and the note id for one to many relationship. so if you want to get all notes for a client with an id of 1; your sql will look like this
SELECT note_id FROM client_note WHERE clientId = 1;
then you can use the note id's you get to query the note table for the actual note objects;
client
int id;
int nameId (foreignKey referencing name.id);
name
int id;
String ClientName;
note
int id;
String ClientNote;
client_note
int id;
int clientId (foreignKey referencing client.id);
int noteId (foreignKey referencing note.id);
Hope this helps?

I think everyone misunderstood my question and I don't blame everyone. I love that my question gets notice and I thank everyone who gave their suggestion to me.
I found the answer here if anyone have the same problem as me:
multiple one-to-many relations ResultSetExtractor
Again, thank you everyone and have a nice day.

Related

How to mask out some field in grpc response when logging

I want to mask out some field when logging.
is there any good way to mask out?
after message is served to client.
I'm trying to mask out some field before logging
how do you mask out field?
--served to client
message {
id : "user",
phoneNumber : "12341234"
}
--logged response
message {
id : "user",
phoneNumber : "12******"
}
I don't think there is a way to alter the data like you mentioned, however there is a way to hide field that you don't want to print in the log (I assume its for security reasons). The way yo do that is with FieldMask.
So for the sake of the example I took the liberty of adding another field to the schema I assume you have:
message User {
string id = 1;
string name = 2;
string phone_number = 3;
}
So now let's say that I don't want to show the phone number of my users, I can use a FieldMask to keep only id and name. So first we build a FieldMask (you'll need to add this dependency for accessing FieldMaskUtil):
public static final FieldMask WITH_ID_AND_NAME =
FieldMaskUtil.fromFieldNumbers(User.class,
User.ID_FIELD_NUMBER,
User.NAME_FIELD_NUMBER);
then what I can do is merge this FieldMask with an existing object:
private static void log(User user, FieldMask fieldMask) {
User.Builder userWithMaskedFields = User.newBuilder();
FieldMaskUtil.merge(fieldMask, user, userWithMaskedFields);
System.out.println(userWithMaskedFields);
}
public static void main(String[] args) {
User user = User.newBuilder()
.setId("user")
.setName("a name")
.setPhoneNumber("12341234")
.build();
System.out.println("---Without FieldMask---");
System.out.println(user);
System.out.println("---With FieldMask---");
log(user, WITH_ID_AND_NAME);
}
This will give me the following output:
---Without FieldMask---
id: "user"
name: "a name"
phone_number: "12341234"
---With FieldMask---
id: "user"
name: "a name"
I hope that helps, let me know if you need more details, I can update my answer.

UnrecognizedPropertyException map to JSONObject

currently I am programming a stocks project for my Bachelor degree, but I'm stuck.
For Frontend I use Angular and Backend Java with a Postgres DB.
I'm getting a 500 response when I try to update a row in my DB. The error is
org.glassfish.jersey.server.ContainerException: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "empty" (class org.json.JSONObject), not marked as ignorable (0 known properties: ])
In Angular I have a Watchlist Interface, which I want to update with an additional investment in it, send the new Watchlist to the backend, to store it in the db.
export interface Watchlist{
id?: number,
userid?: number,
watchlist?: any
}
on button click in the template I access the following method.
hinzufuegenWatchlist(){
this.mainComponent.watchlist.watchlist[this.isin] = this.investment;
this.watchlistService.sendRequestUpdateWatchlist(this.mainComponent.watchlist)
.subscribe(response => console.log('Investment zur Watchlist hinzugefĆ¼gt'));
}
sendRequestUpdateWatchlist(watchlist : Watchlist){
return this.http
.put<Investment>(this.getServiceURL().concat("updateWatchlist"), JSON.stringify(watchlist),this.getOptions())
}
In the backend I use a JSONObject for the watchlist. The idea is to store every investment with its isin as an accessible key, to easily check whether its already stored or not. And easily store the whole watchlist in one row in the DB:
public class Watchlist {
private int id;
private int userid;
private JSONObject watchlist;
#PUT
#Path("updateWatchlist")
#Consumes({MediaType.APPLICATION_JSON})
public void updateWatchlist(Watchlist watchlist){
try {
String sql = "UPDATE watchlistdb set watchlist=? where id=?";
PreparedStatement ps = DatabaseHelper.connectDatabase().prepareStatement(sql);
ps.setString(1,watchlist.getWatchlist().toString());
ps.setInt(2,watchlist.getId());
ps.execute();
} catch (Exception e){
e.printStackTrace();
}
}
My first thoughts are, that it can't be mapped because it isn't a standard Java type like string or int. But how can I solve that problem?
Thank you for your time and help!

Hibernate is returning query result in lower case

I am building a URL shortener service. while retrieving the long URL from short url hibernate is automatically changing the case to smaller case. for example, In my SQL DB I have two rows one has data with "bN" & another one has "bn"
image of table:
https://i.stack.imgur.com/lwPjD.png
my table looks like this
id | longurl | shorturl |
1 | http://..| bn |
2 | httP://..| bN |
I am querying for "bN" but hibernate is giving me the result of "bn".
Here is the definition of LINK object
package com.saikat.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name="urldetails")
public class Link {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name ="id")
private int key;
#Column(name ="shortUrl")
private String shortUrl;
#Column(name ="longUrl")
private String longUrl;
public int getKey() {
return key;
}
public void setKey(int key) {
this.key = key;
}
public String getShortUrl() {
return shortUrl;
}
public void setShortUrl(String shortUrl) {
this.shortUrl = shortUrl;
}
public String getLongUrl() {
return longUrl;
}
public void setLongUrl(String longUrl) {
this.longUrl = longUrl;
}
}
below is the code for fetching details
#Transactional
public Link getLink(String Link) {
Session currentSession = sessionFactory.getCurrentSession();
//Link l =currentSession.get(Link.class,8);
Query query=currentSession.createQuery("from Link where shortUrl= :shortUrl");
query.setParameter("shortUrl", Link);
List<Link> link = query.getResultList();
Link l = new Link();
try {
l = link.get(0);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return l;
}
In the function getLink, I am passing the short link which is "bN". but when hibernate is returning the result it is giving the result of "bn" row. I have checked it in debugging mode.
image of debuging mode:
debug mode 1
debug mode 2
debug mode 3
Here is the log of query
Hibernate: select link0_.id as id1_0_, link0_.longUrl as longurl2_0_, link0_.shortUrl as shorturl3_0_ from urldetails link0_ where link0_.shortUrl=?
13:55:11,135 TRACE BasicBinder:64 - binding parameter [1] as [VARCHAR] - [bN]
13:55:11,138 TRACE BasicExtractor:60 - extracted value ([id1_0_] : [INTEGER]) - [1]
13:55:11,146 TRACE BasicExtractor:60 - extracted value ([longurl2_0_] : [VARCHAR]) - [https://www.google.com]
13:55:11,146 TRACE BasicExtractor:60 - extracted value ([shorturl3_0_] : [VARCHAR]) - [bn]
13:55:11,147 TRACE BasicExtractor:60 - extracted value ([id1_0_] : [INTEGER]) - [2]
13:55:11,147 TRACE BasicExtractor:60 - extracted value ([longurl2_0_] : [VARCHAR]) - [https://stackoverflow.com/questions/68155013/hibernate-is-returning-query-result-in-lower-case?noredirect=1#comment120462353_68155013]
13:55:11,147 TRACE BasicExtractor:60 - extracted value ([shorturl3_0_] : [VARCHAR]) - [bN]
I am using MYSQL & have changed the collation of my DB to latin1_general_cs
In log I observed that hibernate is querying bN which is correct bt the result is coming for both 'bn' & 'bN'
This is an interesting feature of MySQL.
nonbinary string comparisons are case-insensitive by default
You can switch to a binary string:
For binary strings (BINARY, VARBINARY, BLOB), comparisons use the numeric values of the bytes in the operands; this means that for alphabetic characters, comparisons are case-sensitive.
More options and an explanation why BINARY is not what you want can be found in How can I make SQL case sensitive string comparison on MySQL?

Spring Boot REST controller validation

I have a working spring REST web-service and i wanted to add validation for handling different scenarios, i have tried implementing if clauses in my service class and that did not seem efficient one issue i am currently having is whenever i add a new record by sending an existing primary key(auto generated id) JPA seems to update the existing record but when i send non existing id a new record is created but not using an id i sent, an auto generated is used. Here is the code:
Author
#PostMapping("/authors")
#ResponseStatus(HttpStatus.CREATED)
public String addAnAuthor(#Valid #RequestBody Author author){
return authorService.addAuthor(author);
}
Author service
public String addAuthor(Author author) {
if(authorRepository.existsByFirstNameAndLastName(author.getFirstName(),author.getLastName())){
return "Author or Author ID Already Exists";
}
else{
authorRepository.save(author);
return "Created";
}
}
If i send a request without an ID same will be created, but if i send next one with existing id and different first name (to pass validation on first name) it will overwrite an existing record. post man request:
{
"firstName": "Jane",
"lastName": "Doe"
}
{
"id":25,
"firstName": "John",
"lastName": "Doe"
}
{
"id":85,
"firstName": "Mike",
"lastName": "Doe"
}
The first one will create a new record and if we assume it's id is 25 and send the second one, the first record will be overwritten, for the third one a new record will be created but with id 26 (auto incremented) ignoring the forwarded id.
I think there is a validation of some sort inside there, i am wondering if i could leverage this and why is it accepting id if it is an auto generated value?
The issue is in the flow you are using:-
Consider this:-
_______________________________
| ID | firstname | lastname |
--------------------------------
| 1 | john | cena |
--------------------------------
| 2 | john | johnson |
--------------------------------
The two users already are there now you are thinking if you are updating the user with 1 on the basis of only firstname and lastname without id, then when your flow is getting called you are actually passing (example john and mayer as firstname and lastname respectively) and when the function existsByFirstNameAndLastName() is called, you are actually passing the value like existsByFirstNameAndLastName("john","mayer") which will definitely not exist and thus causing a new save rather than update(the code for which does not exist already)
--------------------------------
| 3 | john | mayer |
--------------------------------
and an entry like above created
authorRepository.existsByFirstNameAndLastName(author.getFirstName(),author.getLastName())
You are using it to get the Author based on new(it will always be new as per your code) firstname and lastname values which have been updated in the request you are sending(you or your user thinks as if it has updated the values), and in your case the author will definitely not exist.
My suggestions:-
Have separate methods for create and update flows like below:-
For Creating the Author:-
Controller Method
#PostMapping("/authors")
#ResponseStatus(HttpStatus.CREATED)
public String addAnAuthor(#Valid #RequestBody Author author){
return authorService.addAuthor(author);
}
Service Method
public String addAuthor(Author author) {
if(authorRepository.existsByFirstNameAndLastName(author.getFirstName(),author.getLastName())){
return "Author or Author ID Already Exists";
}
else{
authorRepository.save(author);
return "Created";
}
}
For Updating the Author:-
Controller Method
#PutMapping("/authors")
#ResponseStatus(HttpStatus.CREATED) // you can have this changed as per your convention in the applications, you may use HttpStatus.ACCEPTED(202), HttpStatus.CREATED(201), HttpStatus.NO_CONTENT(204) or HttpStatus.OK(200)
public String updateAnAuthor(#Valid #RequestBody Author author){
return authorService.updateAuthor(author);
}
Service Method
public String updateAuthor(Author author) {
if(authorRepository.existsByIdAndFirstNameAndLastName(author.getId(),author.getFirstName(),author.getLastName())){
return "Author or Author ID Already Exists";
}
else{
Author existingAuthor = authorRepository.findByID(author.getID());// you will have to check as per your spring hibernate versions, as this will vary as per versions
existingAuthor.setFirstName(author.getFirstName()); // updating the FirstName field
existingAuthor.setLastName(author.getLastName()); // updating the LastName field
authorRepository.update(existingAuthor); // use appropriate method here it could be merge or update, depends on the type of configuration
return "Updated";
}
}

JPA double relation with the same Entity

I have these Entities:
#Entity
public class Content extends AbstractEntity
{
#NotNull
#OneToOne(optional = false)
#JoinColumn(name = "CURRENT_CONTENT_REVISION_ID")
private ContentRevision current;
#OneToMany(mappedBy = "content", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ContentRevision> revisionList = new ArrayList<>();
}
#Entity
public class ContentRevision extends AbstractEntity
{
#NotNull
#ManyToOne(optional = false)
#JoinColumn(name = "CONTENT_ID")
private Content content;
#Column(name = "TEXT_DATA")
private String textData;
#Temporal(TIMESTAMP)
#Column(name = "REG_DATE")
private Date registrationDate;
}
and this is the db mapping:
CONTENT
+-----------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------------+--------------+------+-----+---------+----------------+
| ID | bigint(20) | NO | PRI | NULL | auto_increment |
| CURRENT_CONTENT_REVISION_ID | bigint(20) | NO | MUL | NULL | |
+-----------------------------+--------------+------+-----+---------+----------------+
CONTENT_REVISION
+-----------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------------+--------------+------+-----+---------+----------------+
| ID | bigint(20) | NO | PRI | NULL | auto_increment |
| REG_DATE | datetime | YES | | NULL | |
| TEXT_DATA | longtext | YES | | NULL | |
| CONTENT_ID | bigint(20) | NO | MUL | NULL | |
+-----------------------------+--------------+------+-----+---------+----------------+
I have also these requirements:
Content.current is always a member of Content.revisionList (think about Content.current as a "pointer").
Users can add a new ContentRevision to an existing Content
Users can add a new Content with an initial ContentRevision (cascade persist)
Users can change Content.current (move the "pointer")
Users can modify Content.current.textData, but saves Content (cascade merge)
Users can delete ContentRevision
Users can delete Content (cascade remove to ContentRevision)
Now, my questions are:
Is this the best approach? Any best practice?
Is it safe to cascade merge when the same entity is referenced twice? (Content.current is also Content.revisionList[i])
Are Content.current and Content.revisionList[i] the same instance? (Content.current == Content.revisionList[i] ?)
Thanks
#jabu.10245 I'm very grateful for your effort. Thank you, really.
However, there's a problematic (missing) case from your tests: when you run it inside a container using CMT:
#RunWith(Arquillian.class)
public class ArquillianTest
{
#PersistenceContext
private EntityManager em;
#Resource
private UserTransaction utx;
#Deployment
public static WebArchive createDeployment()
{
// Create deploy file
WebArchive war = ShrinkWrap.create(WebArchive.class, "test.war");
war.addPackages(...);
war.addAsResource("persistence-arquillian.xml", "META-INF/persistence.xml");
war.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
// Show the deploy structure
System.out.println(war.toString(true));
return war;
}
#Test
public void testDetached()
{
// find a document
Document doc = em.find(Document.class, 1L);
System.out.println("doc: " + doc); // Document#1342067286
// get first content
Content content = doc.getContentList().stream().findFirst().get();
System.out.println("content: " + content); // Content#511063871
// get current revision
ContentRevision currentRevision = content.getCurrentRevision();
System.out.println("currentRevision: " + currentRevision); // ContentRevision#1777954561
// get last revision
ContentRevision lastRevision = content.getRevisionList().stream().reduce((prev, curr) -> curr).get();
System.out.println("lastRevision: " + lastRevision); // ContentRevision#430639650
// test equality
boolean equals = Objects.equals(currentRevision, lastRevision);
System.out.println("1. equals? " + equals); // true
// test identity
boolean same = currentRevision == lastRevision;
System.out.println("1. same? " + same); // false!!!!!!!!!!
// since they are not the same, the rest makes little sense...
// make it dirty
currentRevision.setTextData("CHANGED " + System.currentTimeMillis());
// perform merge in CMT transaction
utx.begin();
doc = em.merge(doc);
utx.commit(); // --> ERROR!!!
// get first content
content = doc.getContentList().stream().findFirst().get();
// get current revision
currentRevision = content.getCurrentRevision();
System.out.println("currentRevision: " + currentRevision);
// get last revision
lastRevision = content.getRevisionList().stream().reduce((prev, curr) -> curr).get();
System.out.println("lastRevision: " + lastRevision);
// test equality
equals = Objects.equals(currentRevision, lastRevision);
System.out.println("2. equals? " + equals);
// test identity
same = currentRevision == lastRevision;
System.out.println("2. same? " + same);
}
}
since they are not the same:
if I enable cascading on both properties, an Exception is thrown
java.lang.IllegalStateException:
Multiple representations of the same entity [it.shape.edea2.jpa.ContentRevision#1] are being merged.
Detached: [ContentRevision#430639650];
Detached: [ContentRevision#1777954561]
if I disable cascade on current, the change get lost.
the strange thing is that running this test outside the container results in successful execution.
Maybe it's lazy loading (hibernate.enable_lazy_load_no_trans=true), maybe something else, but it's definitely NOT SAFE.
I wonder if there's a way to get the same instance.
Is it safe to cascade merge when the same entity is referenced twice?
Yes. If you manage an instance of Content, then it's Content.revisionList and Content.current are managed as well. Changes in any of those will be persisted when flushing the entity manager. You don't have to call EntityManager.merge(...) manually, unless you're dealing with transient objects that need to be merged.
If you create a new ContentRevision, then call persist(...) instead of merge(...) with that new instance and make sure it has a managed reference to the parent Content, also add it to the content's list.
Are Content.current and Content.revisionList[i] the same instance?
Yes, should be. Test it to be sure.
Content.current is always a member of Content.revisionList (think about Content.current as a "pointer").
You could do that check in in SQL with a check constraint; or in Java, although you'd have to be sure the revisionList is fetched. By default it's lazy fetched, meaning Hibernate will run another query for this list if you access the getRevisionList() method. And for that you need a running transaction, otherwise you'll be getting a LazyInitializationException.
You could instead load the list eagerly, if that's what you want. Or you could define a entity graph to be able to support both strategies in different queries.
Users can modify Content.current.textData, but saves Content (cascade merge)
See my first paragraph above, Hibernate should save changes on any managed entity automatically.
Users can delete ContentRevision
if (content.getRevisionList().remove(revision))
entityManager.remove(revision);
if (revision.equals(content.getCurrentRevision())
content.setCurrentRevision(/* to something else */);
Users can delete Content (cascade remove to ContentRevision)
Here I'd prefer to ensure that in the database schema, for instance
FOREIGN KEY (content_id) REFERENCES content (id) ON DELETE CASCADE;
UPDATE
As requested, I wrote a test. See this gist for the implementations of Content and ContentRevision I used.
I had to make one important change though: Content.current cannot really be #NotNull, especially not the DB field, because if it were, then we couldn't persist a content and revision at the same time, since both have no ID yet. Hence the field must be allowed to be NULL initially.
As a workaround I added the following method to Content:
#Transient // ignored in JPA
#AssertTrue // javax.validation
public boolean isCurrentRevisionInList() {
return current != null && getRevisionList().contains(current);
}
Here the validator ensures that the there is always a non-null current revision and that it is contained in the revision list.
Now here are my tests.
This one proves that the references are the same (Question 3) and that it is enough to persist content where current and revisionList[0] is referencing the same instance (question 2):
#Test #InSequence(0)
public void shouldCreateContentAndRevision() throws Exception {
// create java objects, unmanaged:
Content content = Content.create("My first test");
assertNotNull("content should have current revision", content.getCurrent());
assertSame("content should be same as revision's parent", content, content.getCurrent().getContent());
assertEquals("content should have 1 revision", 1, content.getRevisionList().size());
assertSame("the list should contain same reference", content.getCurrent(), content.getRevisionList().get(0));
// persist the content, along with the revision:
transaction.begin();
entityManager.joinTransaction();
entityManager.persist(content);
transaction.commit();
// verify:
assertEquals("content should have ID 1", Long.valueOf(1), content.getId());
assertEquals("content should have one revision", 1, content.getRevisionList().size());
assertNotNull("content should have current revision", content.getCurrent());
assertEquals("revision should have ID 1", Long.valueOf(1), content.getCurrent().getId());
assertSame("current revision should be same reference", content.getCurrent(), content.getRevisionList().get(0));
}
The next ensures that it's still true after loading the entity:
#Test #InSequence(1)
public void shouldLoadContentAndRevision() throws Exception {
Content content = entityManager.find(Content.class, Long.valueOf(1));
assertNotNull("should have found content #1", content);
// same checks as before:
assertNotNull("content should have current revision", content.getCurrent());
assertSame("content should be same as revision's parent", content, content.getCurrent().getContent());
assertEquals("content should have 1 revision", 1, content.getRevisionList().size());
assertSame("the list should contain same reference", content.getCurrent(), content.getRevisionList().get(0));
}
And even when updating it:
#Test #InSequence(2)
public void shouldAddAnotherRevision() throws Exception {
transaction.begin();
entityManager.joinTransaction();
Content content = entityManager.find(Content.class, Long.valueOf(1));
ContentRevision revision = content.addRevision("My second revision");
entityManager.persist(revision);
content.setCurrent(revision);
transaction.commit();
// re-load and validate:
content = entityManager.find(Content.class, Long.valueOf(1));
// same checks as before:
assertNotNull("content should have current revision", content.getCurrent());
assertSame("content should be same as revision's parent", content, content.getCurrent().getContent());
assertEquals("content should have 2 revisions", 2, content.getRevisionList().size());
assertSame("the list should contain same reference", content.getCurrent(), content.getRevisionList().get(1));
}
SELECT * FROM content;
id | version | current_content_revision_id
----+---------+-----------------------------
1 | 2 | 2
UPDATE 2
It was hard to reproduce that situation on my machine, but I got it to work. Here is what I've done so far:
I changed all #OneToMany relations to use lazy fetching (the default) and rerun the following test case:
#Test #InSequence(3)
public void shouldChangeCurrentRevision() throws Exception {
transaction.begin();
entityManager.joinTransaction();
Document document = entityManager.find(Document.class, Long.valueOf(1));
assertNotNull(document);
assertEquals(1, document.getContentList().size());
Content content = document.getContentList().get(0);
assertNotNull(content);
ContentRevision revision = content.getCurrent();
assertNotNull(revision);
assertEquals(2, content.getRevisionList().size());
assertSame(revision, content.getRevisionList().get(1));
revision.setTextData("CHANGED");
document = entityManager.merge(document);
content = document.getContentList().get(0);
revision = content.getCurrent();
assertSame(revision, content.getRevisionList().get(1));
assertEquals("CHANGED", revision.getTextData());
transaction.commit();
}
The test passed with lazy fetching. Note that lazy fetching requires it to be executed within a transaction.
For some reason the content revision instance you're editing is not the same as the one in the one-to-many list. To reproduce that I've modified my test as follows:
#Test #InSequence(4)
public void shouldChangeCurrentRevision2() throws Exception {
transaction.begin();
Document document = entityManager.find(Document.class, Long.valueOf(1));
assertNotNull(document);
assertEquals(1, document.getContentList().size());
Content content = document.getContentList().get(0);
assertNotNull(content);
ContentRevision revision = content.getCurrent();
assertNotNull(revision);
assertEquals(2, content.getRevisionList().size());
assertSame(revision, content.getRevisionList().get(1));
transaction.commit();
// load another instance, different from the one in the list:
revision = entityManager.find(ContentRevision.class, revision.getId());
revision.setTextData("CHANGED2");
// start another TX, replace the "current revision" but not the one
// in the list:
transaction.begin();
document.getContentList().get(0).setCurrent(revision);
document = entityManager.merge(document); // here's your error!!!
transaction.commit();
content = document.getContentList().get(0);
revision = content.getCurrent();
assertSame(revision, content.getRevisionList().get(1));
assertEquals("CHANGED2", revision.getTextData());
}
And there, I got exactly your error. Then I modified the cascading setting on the #OneToMany mapping:
#OneToMany(mappedBy = "content", cascade = { PERSIST, REFRESH, REMOVE }, orphanRemoval = true)
private List<ContentRevision> revisionList;
And the error disappeared :-) ... because I removed CascadeType.MERGE.

Categories

Resources