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 a entity class with an auto increment field in it.I want to read auto increment id assigned to it when it gets persisted
Annotations on getter are as 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 entities as
id 1 subid 0
id 1 subid 1
id 1 subid 2
id 2 subid 0
subid is default 0 in database and i am incrementing it programmatically on updates to that row. I tried the solution as in this SO post JPA - Returning an auto generated id after persist()
@Transactional
@Override
public void daoSaveEntity(SomeEntity entity) {
entityManager.persist(entity);
}
Now outside this transaction I am trying to get the auto increment id assigned
@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 as 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 send N concurrent updates from N threads for entity with Id 1 and few other properties as 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 with With @Id
on id and @Id
annotation on subid and `entityManager.persist' in update
When i ran with 300 threads some failed with connection exception "too many connections" The databse state is
id 1 subid 0
id 1 subid 1
id 1 subid 2
.. ....
id 1 subid 150
the subid is always incremental ,the race condition is only that which one will be ACTIVE is undefined because of race condition
Case 2 with With @Id
on id and @Id
annotation on subid and `entityManager.merge' in update
id 1 subid 0 id 1 subid 0 id 2 subid 0 .. .... id 151 subid 0 (Perhaps just a co-incidence that one more thread than case 1 was successful? )
Case 3 With @GeneratedValue
and @Id
on id and **NO @Id
annotation on subid and entityManager.persist in update**
exception -- Detached entity passed to persist
Case 3 With @GeneratedValue
and @Id
on id and **NO @Id
annotation on subid and entityManager.merge in update**
if update is run sequentially the database state is
id 1 subid 0
after next update
id 1 subid 1
after each update same row is updated (leading to only one row at a time)
id 1 subid 2
Case 4 same as Case 3 with Concurrent updates if run concurrently(with 300 threads) i get the below exception
org.hibernate.HibernateException: More than one row with the given identifier was found: 1
database state is id 1 subid 2 (Only one thread would have been successful but because of race condition updated subid from 0 to 2 )
Case 5 With @GeneratedValue
and @Id
on id and @Id
annotation on subid
Create also fails with subid org.hibernate.PropertyAccessException: IllegalArgumentException occurred while calling setter of SomeEntity.id
Please explain the causes.From the javadoc of methods i know that
persist - Make an instance managed and persistent.
merge - Merge the state of the given entity into the current persistence context.
My question is more towards how hibernate manages the annotations. Why is there be a detached entity exception in case 3 when the session is not closed yet? Why is there a IllegalArgumentException in case 5 ?
I am using hibernate 3.6 mysql 5 and Spring 4 Also please suggest a way achieve such incremental id and subid.(Using custom SelectGenerator ,with a demo implementation or any other way without doing a column concat)
Since the id
field is already unique and auto incremented, you don't need a composite id in this case so your entity can look like this:
@Id
@Column(name = "id")
public long getId() {
return id;
}
@Column(name = "subid")
public int getSubid() {
return subid;
}
The entity can be fetched by id using the entity manager:
entityManager.find(MyEntity.class, entityId);
or you could fetch the entity using a query that takes both the id
and the 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 fetch the id from a database column, which is useful when the database generates the id using a trigger.
Unfortunately, it doesn't work with composite ids, so you wither wrote your own extended SelectGenerator
or use a single string id_sub_id
column that combines the id and sub-id into a single VARCHAR column:
'1-0'
'1-1'
'2-0'
'2-1'
You have to write a database trigger to update the two columns using a database specific stored procedure and aggregate the two columns into the VARCHAR one. You then map the aggregated column using the standard SelectGenerator
to a 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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With