Explain behavior when matching an automatically added compound identifier sequence with Hibernate

I have a table

CREATE TABLE `SomeEntity` ( `id` int(11) NOT NULL AUTO_INCREMENT, `subid` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`,`subid`), 

I have an entity class with an auto-increment field in it. I want to read the auto increment identifier assigned to it when it is saved

Getter annotations are given below.

  private long id; private int subid; @Id @GeneratedValue **//How do i correct this to have multiple rows with same id and different subid** @Column(name = "id") public long getId() { return id; } @Id @Column(name = "subid") public int getSubid() { return subid; } 

I want to have objects like

 id 1 subid 0 id 1 subid 1 id 1 subid 2 id 2 subid 0 

The default subid is 0 in the database, and I am gradually increasing it for updates on this line. I tried a solution, as in this SO JPA post - returning an automatically generated identifier after saving ()

  @Transactional @Override public void daoSaveEntity(SomeEntity entity) { entityManager.persist(entity); } 

Now, outside of this transaction, I'm trying to get the auto increment id

  @Override public long serviceSaveEntity(SomeEntity entity) { dao.daoSaveEntity(entity); return entity.getId(); } 

I am calling this from a web service

  @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response createEntity(SomeEntity entity) { 

The update method is given below.

  @Transactional public void updateReportJob(SomeEntity someEntity) { Query query = entityManager .createQuery("UPDATE SomeEntity SET state=:newState WHERE id = :id"); query.setParameter("newState","PASSIVE"); query.setParameter("id", id); query.executeUpdate(); double rand = Math.random(); int i = (int) (rand * 300); try { Thread.sleep(i); //only to simulate concurrency issues } catch (InterruptedException e) { e.printStackTrace(); } List<Integer> resList = entityManager.createQuery("select max(subid) from SomeEntity WHERE id = :id") .setParameter("id", jobId).getResultList(); // Increment old subid by 1 int subid = resList.get(0); SomeEntity.setsubid(subid + 1); SomeEntity.setState("ACTIVE"); // entityManager.merge(SomeEntity); entityManager.persist(SomeEntity); } 

I am sending N simultaneous updates from N threads for an object with Id 1 and several other properties as shown below

SomeEnity entity = new SomeEntity ();

  entity.setId(1); long num = Thread.currentThread().getId(); entity.setFieldOne("FieldOne" + num); entity.setFieldTwo("" + i); entity.setFieldThree("" + j); i++; j++; 

Case 1 using @Id in id and @Id annotations for subid and `entityManager.persist 'in update When I worked with 300 threads, some with connection error' too many connections' Database status

  id 1 subid 0 id 1 subid 1 id 1 subid 2 .. .... id 1 subid 150 

the subad is always incremental, the race condition is only what will be ACTIVE, undefined due to the race condition

Case 2 with @Id in id and @Id annotations in subid and `entityManager.merge 'in update

id 1 subad 0 id 1 subid 0 id 2 subid 0 ...... id 151 subid 0 (Perhaps it is only a coincidence that another thread than case 1 was successful?)

Case 3 With @GeneratedValue and @Id in id and ** NO @Id annotation by subid and entityManager.persist in update ** exception - A separate object passed for saving

Case 3 With @GeneratedValue and @Id in id and ** NO @Id annotation by subid and entityManager.merge in the update ** if the update is performed sequentially, the state of the database

 id 1 subid 0 

after next update

 id 1 subid 1 

after updating each new version of the update (which results in only one line at a time)

 id 1 subid 2 

Case 4, similar to case 3 with parallel updates if run simultaneously (with 300 threads), I get the following exception

  org.hibernate.HibernateException: More than one row with the given identifier was found: 1 
Database status

id 1 subid 2 (Only one thread would be successful, but due to an updated rank status from 0 to 2)

Case 5 From @GeneratedValue and @Id to id and @Id annotations to subad
Create also fails with subid org.hibernate.PropertyAccessException: IllegalArgumentException occurred while calling setter of SomeEntity.id

Please explain the reasons. From javadoc methods, I know that

persist - make the instance manageable and persistent.

merge - merge the state of this object into the current save context.

My question is more about how hibernate manages annotations. Why, in case 3, is there a single object exception if the session is not already closed? Why is there a IllegalArgumentException in case 5?

I am using hibernate 3.6 mysql 5 and Spring 4 Also try specifying such incremental id and subad. (Using a custom SelectGenerator with a demo implementation or in any other way without executing the concat column)

+11
java spring mysql hibernate jpa
Jul 11 '15 at 21:38
source share
3 answers

Since the id field is already unique and automatically grows, in this case you do not need a composite identifier so that your object can look like this:

 @Id @Column(name = "id") public long getId() { return id; } @Column(name = "subid") public int getSubid() { return subid; } 

An object can be retrieved using an identifier using the entity manager:

 entityManager.find(MyEntity.class, entityId); 

or you can get the object using a request that accepts both id and subid :

 MyEntity myEntity = entityManager.createTypeQuery("select me from MyEntity where id = :id and subid = :subid", MyEntity.class) .setParameter("id", entityId) .setParameter("subid", entitySubId) .getSingleResult(); 

Hibernate also has a SelectGenerator that can retrieve an identifier from a database column, which is useful when the database generates an identifier using a call.

Unfortunately, it does not work with compound identifiers, so you wilted your own extended SelectGenerator or used the id_sub_id column, which combines id and sub-id into one VARCHAR column:

 '1-0' '1-1' '2-0' '2-1' 

You need to write a database trigger to update two columns using a database-specific stored procedure and merge the two columns in VARCHAR. Then you map the aggregated column using the standard SelectGenerator in the String field:

 @Id @Column(name = "id_sub_id") @GeneratedValue( strategy = "trigger" ) @GenericGenerator( name="trigger", strategy="org.hibernate.id.SelectGenerator", parameters = { @Parameter( name="keys", value="id_sub_id" ) } ) public String getId() { return id; } 
+3
Jul 14 '15 at 6:07
source share

Let's say I have several books with id and version. The identifier belongs to the book, which may have many updated versions, the last of which is the current one, which will be mainly requested. What would be a better approach? Should I use a one-to-many mapping

See the design diagram below to see how to display it correctly:

enter image description here

Saving book:

 @Transactional public void saveBook(Book book) { em.persist(book); } 

Saving a book version:

 @Transactional public void saveBookVersion(BookVersion bookVersion) { Book book = em.find(Book.class, bookVersion.getBook().getId()); bookVersion.setBook(book); book.setLatestBookVersion(bookVersion); em.persist(bookVersion); } 

Get the latest version of the book:

 @Transactional(readOnly=true) public Book getLatestBookVersion(Long bookId) { // it is enough if lastestBookVersion is loaded eagerly return em.find(Book.class, bookId); } 

Displaying the logic table table schema above:

enter image description here

+2
Jul 15 '15 at 9:39
source share

Just because no one mentioned it.

I have a table

 CREATE TABLE `SomeEntity` ( `id` int(11) NOT NULL AUTO_INCREMENT, `subid` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`,`subid`), 

I have an entity class with an auto-increment field in it.

[..]

The default subid is 0 in the database, and I am gradually increasing it for updates on this line.

This is very similar to version control to me.

Actually there is an annotation that will do exactly what you want, without having to write code:

@Version

The vertex object is marked with the @Version annotation as shown in the following code snippet:

 public class User { @ID Integer id; @Version Integer version; } 

and the corresponding database schema has a version column, for example, created by the following SQL statement:

 CREATE TABLE `User` ( `id` NUMBER NOT NULL, `version` NUMBER, PRIMARY KEY (`id`) ); 

The version attribute can be int, short, long, or timestamp. It increases when a transaction successfully completes . This results in an SQL operation, for example:

 UPDATE User SET ..., version = version + 1 WHERE id = ? AND version = readVersion 

source: blogs.oracle.com (highlight)

I changed the example a bit. I recommend using field types Integer , Long , etc. For database fields. Even an id that is NOT NULL will be null before you save the object for the first time. I believe that using box types makes it explicit that these fields can and will be null .

0
Jun 04 '17 at 23:27
source share



All Articles