Migrate from Hibernate 4 to 5 - java

I have some problems with migration from hibernate 4.3 to hibernate 5.
I'm working on a major project where we use SequenceHiLoGenerator to generate id which is deprecated now. How update this without major changes?
This is my code with hibernate 4.3.
public class KSequenceGenerator extends SequenceHiLoGenerator {
#Override
public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session);
return id != null ? id : super.generate(session, object);
}
In EJB:
#GenericGenerator(name="id_gen", strategy="com.or.nk.ob.base.KSequenceGenerator", parameters = {#org.hibernate.annotations.Parameter(name = KSequenceGenerator.SEQUENCE, value = "building_id_seq")})
now i have:
public class KSequenceGenerator extends SequenceStyleGenerator {
public static final String SEQUENCE = "sequence";
#Override public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session);
return id != null ? id : super.generate(session, object);
}
}
id:
#Id
#Column(name = "ID")
#GeneratedValue(generator = "id_gen", strategy = GenerationType.SEQUENCE)
public Long getId() {
return id;
}

Related

MongoDB repository save() do the insert instead of update

I am writing a PUT request API with spring and mongodb. But the save() inserts a new object instead of update the current one.
#Document("Test")
public class Expense {
#Field(name = "name")
private String expenseName;
#Field(name = "category")
private ExpenseCategory expenseCategory;
#Field(name = "amount")
private BigDecimal expenseAmount;
public Expense( String expenseName, ExpenseCategory expenseCategory, BigDecimal expenseAmount) {
this.expenseName = expenseName;
this.expenseCategory = expenseCategory;
this.expenseAmount = expenseAmount;
}
public String getExpenseName() {
return expenseName;
}
public void setExpenseName(String expenseName) {
this.expenseName = expenseName;
}
public ExpenseCategory getExpenseCategory() {
return expenseCategory;
}
public void setExpenseCategory(ExpenseCategory expenseCategory) {
this.expenseCategory = expenseCategory;
}
public BigDecimal getExpenseAmount() {
return expenseAmount;
}
public void setExpenseAmount(BigDecimal expenseAmount) {
this.expenseAmount = expenseAmount;
}
}
This is my reporsitory class
public interface ExpenseRepository extends MongoRepository<Expense, String> {
}
This is my Service class which shows how to update the class.
#Service
public class ExpenseService {
private final ExpenseRepository expenseRepository;
public ExpenseService(ExpenseRepository expenseRepository) {
this.expenseRepository = expenseRepository;
}
public void updateExpense(String id, Expense expense){
Expense savedExpense = expenseRepository.findById(id)
.orElseThrow(() -> new RuntimeException(
String.format("Cannot Find Expense by ID %s", id)));
savedExpense.setExpenseName(expense.getExpenseName());
savedExpense.setExpenseAmount(expense.getExpenseAmount());
savedExpense.setExpenseCategory(expense.getExpenseCategory());
expenseRepository.save(savedExpense);
}
}
This is my controller
#RestController
#RequestMapping("/api/expense")
public class ExpenseController {
private final ExpenseService expenseService;
public ExpenseController(ExpenseService expenseService) {
this.expenseService = expenseService;
}
#PutMapping("/{id}")
public ResponseEntity<Object> updateExpense(#PathVariable String id, #RequestBody Expense expense){
expenseService.updateExpense(id, expense);
return ResponseEntity.ok().build();
}
}
As shown in mongodb compass, mongodb auto generates an _id field for every object. So I do not define a id field or use #id annotation to define a primary for the collection. However, in the service class, expenseRepository.findById(id) retrieves the desired object and update it. Why does save() do the insert instead of update? Many thanks.
JPA Can't find the existing entry as no id field id set. You need to add an id field and set generation type to auto.
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;

How to remove an entity without loading its contents?

I have a service with a removeById operation whose goal is to remove an entity from the repository. As I don't want to fetch the contents of the entity before it gets removed, I use getReference() instead of find() to obtain the entity.
An entity named 'MyObject' has a #ManyToOne association with an entity named 'MySource', and the fetch type is set to LAZY.
When I get a reference to an instance of 'MyObject' and I remove that instance from the DB, both 'MyObject' and 'MySource' get hydrated first before the instance is removed.
Sample code:
#Entity
public class MyObject {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
private final MySource source;
public MyObject(MySource source) {
this.source = source;
}
protected MyObject() {
this.source = null;
}
public long id() {
return this.id;
}
public MySource source() {
return this.source;
}
#Override
public boolean equals(Object obj) {
boolean equals = this == obj;
if (!equals && obj instanceof MyObject) {
MyObject other = (MyObject) obj;
equals = other.id() == id;
}
return equals;
}
#Override
public int hashCode() {
return (int) id % 16;
}
}
#Entity
public class MySource {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
public MySource(String name) {
this.name = name;
}
protected MySource() {
this.name = "";
}
#Column
private final String name;
public long id() {
return id;
}
public String name() {
return this.name;
}
#Override
public boolean equals(Object obj) {
boolean equals = this == obj;
if (!equals && obj instanceof MySource) {
MySource other = (MySource) obj;
equals = other.id() == id;
}
return equals;
}
#Override
public int hashCode() {
return (int) id % 16;
}
}
public void removeById(long id) {
em.getTransaction().begin();
MyObject myObjectRef = em.getReference(MyObject.class, id);
em.remove(myObjectRef);
em.getTransaction().commit();
em.clear();
}
When the transaction is committed, I can see the following queries:
SELECT ID, SOURCE_ID FROM MYOBJECT WHERE (ID = ?)
SELECT ID, NAME FROM MYSOURCE WHERE (ID = ?)
DELETE FROM MYOBJECT WHERE (ID = ?)
And more surprisingly, if I change the fetch type from LAZY to EAGER in the 'MyObject' -> 'MySource' association, then I obtain:
DELETE FROM MYOBJECT WHERE (ID = ?)
This time the proxies are not hydrated.
What's wrong with LAZY loading when a proxy is used to remove an entity? And why the proxies are not hydrated when the fetch type is set to EAGER?
Did I miss something, or am I facing some bug in EclipseLink?
(EclipseLink 2.6.4 + static weaving on Java 8 SE).
Additional note
I think that the proxies are hydrated too eagerly. For instance, a statement like this.someAssociatedEntity = other.someAssociatedEntity issues a SELECT to fetch that associated entity, despite the fact that the association is annotated with a LAZY fetch type.
I would expect that a SELECT occurs only when the fields of the associated entity are accessed.
For debug purpose:
Stack trace when the MySource table is deleted just before the 'MyObject' entity is removed:
Error Code: 0
Call: SELECT ID, NAME FROM MYSOURCE WHERE (ID = ?)
bind => [1]
Query: ReadObjectQuery(name="source" referenceClass=MySource )
at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:340)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.processExceptionForCommError(DatabaseAccessor.java:1620)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:676)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:560)
at org.eclipse.persistence.internal.sessions.AbstractSession.basicExecuteCall(AbstractSession.java:2056)
at org.eclipse.persistence.sessions.server.ClientSession.executeCall(ClientSession.java:306)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:242)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:228)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.selectOneRow(DatasourceCallQueryMechanism.java:714)
at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.selectOneRowFromTable(ExpressionQueryMechanism.java:2803)
at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.selectOneRow(ExpressionQueryMechanism.java:2756)
at org.eclipse.persistence.queries.ReadObjectQuery.executeObjectLevelReadQuery(ReadObjectQuery.java:555)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeDatabaseQuery(ObjectLevelReadQuery.java:1175)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:904)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.execute(ObjectLevelReadQuery.java:1134)
at org.eclipse.persistence.queries.ReadObjectQuery.execute(ReadObjectQuery.java:441)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeInUnitOfWork(ObjectLevelReadQuery.java:1222)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1857)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1839)
at org.eclipse.persistence.internal.indirection.QueryBasedValueHolder.instantiate(QueryBasedValueHolder.java:133)
at org.eclipse.persistence.internal.indirection.QueryBasedValueHolder.instantiateForUnitOfWorkValueHolder(QueryBasedValueHolder.java:151)
at org.eclipse.persistence.internal.indirection.UnitOfWorkValueHolder.instantiateImpl(UnitOfWorkValueHolder.java:160)
at org.eclipse.persistence.internal.indirection.UnitOfWorkValueHolder.instantiate(UnitOfWorkValueHolder.java:234)
at org.eclipse.persistence.internal.indirection.DatabaseValueHolder.getValue(DatabaseValueHolder.java:89)
at example.MyObject._persistence_get_source(MyObject.java)
at example.MyObject._persistence_get_source_vh(MyObject.java)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor.getAttributeValueFromObject(MethodAttributeAccessor.java:82)
at org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor.getAttributeValueFromObject(MethodAttributeAccessor.java:61)
at org.eclipse.persistence.mappings.DatabaseMapping.getAttributeValueFromObject(DatabaseMapping.java:657)
at org.eclipse.persistence.mappings.ForeignReferenceMapping.getAttributeValueFromObject(ForeignReferenceMapping.java:1003)
at org.eclipse.persistence.mappings.ObjectReferenceMapping.earlyPreDelete(ObjectReferenceMapping.java:841)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1412)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1531)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitRootUnitOfWork(RepeatableWriteUnitOfWork.java:278)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitAndResume(UnitOfWorkImpl.java:1169)
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:134)
at test.Main.test(Main.java:203)
at test.Main.main(Main.java:42)

Hibernate optimistic locking using version is not working

I am trying to use optimistic locking using the version field and no exception is being thrown when I call the save from the jpa repository. I am new to Spring and hibernate and I am worried that I am setting it up incorrectly.
The libraries i am using are:
hibernate4-maven-plugin version 1.0.2
hibernate-jpa02.0 1.0.1
spring-data-jpa version 1.3.4
So my entity is set up like this:
#Entity
public class MyEntity
{
#Id
protected Long id;
#Version
protected Long version;
protected String name;
public Long getVersion()
{
return version;
}
public void setVersion(Long version)
{
this.version = version;
}
public Long getVersion()
{
return version;
}
public void setVersion(Long version)
{
this.version = version;
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(Long id)
{
this.name = name;
}
}
I pass the version through to the client through my dto and pass it back when i do a save in my MyEntityStoreDao:
#Repository
public class MyEntityStoreDao extends BaseDao<MyEntityStoreDao>
{
private RepositoryManager myRepoManager;
#Autowired
public void setMyRepo(MyEntityRepository myRepo)
{
this.myRepo = myRepo;
}
public MyEntity save(MyEntityDTO dtoToUpdate)
{
Session session = this.Session();
MyEntity myEntity = new MyEntity();
if(dtoToUpdate.getId() > 0) {
myEntity = (MyEntity) session.get(MyEntity.class, dtoToUpdate.getId())
}
myEntity.setName(dtoToUpdate.getName());
MyEntity result = this.myRepo.save(myEntity);
this.repositoryManager.flush(myRepo);
}
}
The repositoryManager is in the BaseDao and is using the org.springframework.data.jpa.repository.JpaRepository.
The version is being updated correctly and incrementing. But when i do an update, I expect when the version being passed through from the DTO to save in the MyEntityStoreDao to not match what is in the database, it would throw a StaleStateException or OptmisticLockingException.
I checked and the versions do not match but the save still occurs. Any help on why this is happening? Thanks
Turn On sql logging by show-sql=true and see if the update query has the required where clause
where version = ?
If such where clause is missing then you need to add annotation #org.hibernate.annotations.Entity(dynamicUpdate = true)
With your updated Code
MyEntity myEntity = new MyEntity(); // You dont need to initalize
if(dtoToUpdate.getId() > 0) {
myEntity = (MyEntity) session.get(MyEntity.class, dtoToUpdate.getId())
}
myEntity.setName(dtoToUpdate.getName());
MyEntity result = this.myRepo.save(myEntity);
you are trying to save the myEntity , Which has recent information (correct version) from database. So you will not get any error. if you want to produce the error please do the following..
public MyEntity save(MyEntityDTO dtoToUpdate)
{
Session session = this.Session();
MyEntity myEntityV1 = null;
MyEntity myEntityV2 = null;
// Getting v1 and V2. at this time both V1 & V2 will have same version ( assume version as 5)
if(dtoToUpdate.getId() > 0) {
myEntityV1 = (MyEntity) session.get(MyEntity.class, dtoToUpdate.getId()) ;
myEntityV2 = (MyEntity) session.get(MyEntity.class, dtoToUpdate.getId()) ;
}
myEntityV1.setName(dtoToUpdate.getName());
// Saving V1 will reflect the increase in version ( actual row will be version of 6)
MyEntity result = this.myRepo.save(myEntityV1);
myEntityV2.setName("some changes"); // change some in V2instance. So that hibernate/Jpa will capture the change
this.myRepo.save(myEntityV2); // You will get exception. because the v2 has version as 5. but the row was updated .
this.repositoryManager.flush(myRepo);
}
So basically when you update the entity in Db, if the entity version (variable in object) is not equal to the version ( field in the table) in database ( before this update) it will throw exception

Java EE 6, EJB 3.1, JPA 2.0 - error in inserting record in database

I am trying to insert a record in the database (using Java EE 6, EJB 3.1, JPA 2.0). I am getting an error that accountTypeId field is null, but i have set it up as autogenerate. Can anyone please suggest what am I doing wrong?
Following is the create table query:
create table example.account_type(
account_type_id INT NOT null PRIMARY KEY GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
account_type_desc varchar(20)
);
Following is the entity class:
EDIT: Updated the entity class as generated by NetBeans which didn't work. I also added #GeneratedValue annotation but still it didn't work.
#Entity
#Table(name = "ACCOUNT_TYPE")
#NamedQueries({
#NamedQuery(name = "AccountType.findAll", query = "SELECT a FROM AccountType a"),
#NamedQuery(name = "AccountType.findByAccountTypeId", query = "SELECT a FROM AccountType a WHERE a.accountTypeId = :accountTypeId"),
#NamedQuery(name = "AccountType.findByAccountTypeDesc", query = "SELECT a FROM AccountType a WHERE a.accountTypeDesc = :accountTypeDesc")})
public class AccountType implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY) // ADDED THIS LINE
#Basic(optional = false)
#NotNull
#Column(name = "ACCOUNT_TYPE_ID")
private Integer accountTypeId;
#Size(max = 50)
#Column(name = "ACCOUNT_TYPE_DESC")
private String accountTypeDesc;
public AccountType() {
}
public AccountType(Integer accountTypeId) {
this.accountTypeId = accountTypeId;
}
public Integer getAccountTypeId() {
return accountTypeId;
}
public void setAccountTypeId(Integer accountTypeId) {
this.accountTypeId = accountTypeId;
}
public String getAccountTypeDesc() {
return accountTypeDesc;
}
public void setAccountTypeDesc(String accountTypeDesc) {
this.accountTypeDesc = accountTypeDesc;
}
#Override
public int hashCode() {
int hash = 0;
hash += (accountTypeId != null ? accountTypeId.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof AccountType)) {
return false;
}
AccountType other = (AccountType) object;
if ((this.accountTypeId == null && other.accountTypeId != null) || (this.accountTypeId != null && !this.accountTypeId.equals(other.accountTypeId))) {
return false;
}
return true;
}
#Override
public String toString() {
return "Entities.AccountType[ accountTypeId=" + accountTypeId + " ]";
}
}
Following is the session bean interface:
#Remote
public interface AccountTypeSessionBeanRemote {
public void createAccountType();
public void createAccountType(String accDesc);
}
Following is the session bean implementation class:
#Stateless
public class AccountTypeSessionBean implements AccountTypeSessionBeanRemote {
#PersistenceContext(unitName="ExamplePU")
private EntityManager em;
#Override
public void createAccountType(String accDesc) {
AccountType emp = new AccountType(accDsc);
try {
this.em.persist(emp);
System.out.println("after persist");
} catch(Exception ex) {
System.out.println("ex: " + ex.getMessage());
}
}
}
Following is the Main class:
public class Main {
#EJB
private static AccountTypeSessionBeanRemote accountTypeSessionBean;
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
accountTypeSessionBean.createAccountType("test");
}
}
Following is the error:
INFO: ex: Object: Entities.AccountType[ accountTypeId=null ] is not a known entity type.
You are not getting an error because of "accountTypeId field is null". As the error message says, the error occurs because "Entities.AccountType[ accountTypeId=null ] is not a known entity type".
The most likely reason is that AccountType is not annotated with #Entity. This problem is likely solved by adding it. Additionally it makes sense to use
#GeneratedValue(strategy = GenerationType.IDENTITY)
instead of AUTO. Auto means that the provider chooses a strategy based on the capabilities of the target database. According to the table definition it seems clear that the preferred strategy is IDENTITY.
I changed my create table query as following:
create table example.account_type(
account_type_id INT NOT null PRIMARY KEY,
account_type_desc varchar(20)
);
Then had to add the following line to the entity class (Netbeans doesn't add that):
#GeneratedValue(strategy = GenerationType.AUTO)

Bypass GeneratedValue in Hibernate (merge data not in db?)

My problem is the same as described in [1] or [2]. I need to manually set a by default auto-generated value (why? importing old data). As described in [1] using Hibernate's entity = em.merge(entity) will do the trick.
Unfortunately for me it does not. I neither get an error nor any other warning. The entity is just not going to appear in the database. I'm using Spring and Hibernate EntityManager 3.5.3-Final.
Any ideas?
Another implementation, way simpler.
This one works with both annotation-based or xml-based configuration: it rely on hibernate meta-data to get the id value for the object. Replace SequenceGenerator by IdentityGenerator (or any other generator) depending on your configuration. (The creation of a decorator instead of subclassing, passing the decorated ID generator as a parameter to this generator, is left as an exercise to the reader).
public class UseExistingOrGenerateIdGenerator extends SequenceGenerator {
#Override
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
Serializable id = session.getEntityPersister(null, object)
.getClassMetadata().getIdentifier(object, session);
return id != null ? id : super.generate(session, object);
}
}
Answer to the exercise (using a decorator pattern, as requested), not really tested:
public class UseExistingOrGenerateIdGenerator implements IdentifierGenerator, Configurable {
private IdentifierGenerator defaultGenerator;
#Override
public void configure(Type type, Properties params, Dialect d)
throws MappingException;
// For example: take a class name and create an instance
this.defaultGenerator = buildGeneratorFromParams(
params.getProperty("default"));
}
#Override
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
Serializable id = session.getEntityPersister(null, object)
.getClassMetadata().getIdentifier(object, session);
return id != null ? id : defaultGenerator.generate(session, object);
}
}
it works on my project with the following code:
#XmlAttribute
#Id
#Basic(optional = false)
#GeneratedValue(strategy=GenerationType.IDENTITY, generator="IdOrGenerated")
#GenericGenerator(name="IdOrGenerated",
strategy="....UseIdOrGenerate"
)
#Column(name = "ID", nullable = false)
private Integer id;
and
import org.hibernate.id.IdentityGenerator;
...
public class UseIdOrGenerate extends IdentityGenerator {
private static final Logger log = Logger.getLogger(UseIdOrGenerate.class.getName());
#Override
public Serializable generate(SessionImplementor session, Object obj) throws HibernateException {
if (obj == null) throw new HibernateException(new NullPointerException()) ;
if ((((EntityWithId) obj).getId()) == null) {
Serializable id = super.generate(session, obj) ;
return id;
} else {
return ((EntityWithId) obj).getId();
}
}
where you basically define your own ID generator (based on the Identity strategy), and if the ID is not set, you delegate the generation to the default generator.
The main drawback is that it bounds you to Hibernate as JPA provider ... but it works perfectly with my MySQL project
Updating Laurent Grégoire's answer for hibernate 5.2 because it seems to have changed a bit.
public class UseExistingIdOtherwiseGenerateUsingIdentity extends IdentityGenerator {
#Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session);
return id != null ? id : super.generate(session, object);
}
}
and use it like this: (replace the package name)
#Id
#GenericGenerator(name = "UseExistingIdOtherwiseGenerateUsingIdentity", strategy = "{package}.UseExistingIdOtherwiseGenerateUsingIdentity")
#GeneratedValue(generator = "UseExistingIdOtherwiseGenerateUsingIdentity")
#Column(unique = true, nullable = false)
protected Integer id;
I`m giving a solution here that worked for me:
create your own identifiergenerator/sequencegenerator
public class FilterIdentifierGenerator extends IdentityGenerator implements IdentifierGenerator{
#Override
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
// TODO Auto-generated method stub
Serializable id = session.getEntityPersister(null, object)
.getClassMetadata().getIdentifier(object, session);
return id != null ? id : super.generate(session, object);
}
}
modify your entity as:
#Id
#GeneratedValue(generator="myGenerator")
#GenericGenerator(name="myGenerator", strategy="package.FilterIdentifierGenerator")
#Column(unique=true, nullable=false)
private int id;
...
and while saving instead of using persist() use merge() or update()
If you are using hibernate's org.hibernate.id.UUIDGenerator to generate a String id I suggest you use:
public class UseIdOrGenerate extends UUIDGenerator {
#Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session);
return id != null ? id : super.generate(session, object);
}
}
According to the Selectively disable generation of a new ID thread on the Hibernate forums, merge() might not be the solution (at least not alone) and you might have to use a custom generator (that's the second link you posted).
I didn't test this myself so I can't confirm but I recommend reading the thread of the Hibernate's forums.
For anyone else looking to do this, above does work nicely. Just a recommendation to getting the identifier from the object rather than having inheritance for each Entity class (Just for the Id), you could do something like:
import org.hibernate.id.IdentityGenerator;
public class UseIdOrGenerate extends IdentityGenerator {
private static final Logger log = Logger.getLogger(UseIdOrGenerate.class
.getName());
#Override
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
if (object == null)
throw new HibernateException(new NullPointerException());
for (Field field : object.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Id.class)
&& field.isAnnotationPresent(GeneratedValue.class)) {
boolean isAccessible = field.isAccessible();
try {
field.setAccessible(true);
Object obj = field.get(object);
field.setAccessible(isAccessible);
if (obj != null) {
if (Integer.class.isAssignableFrom(obj.getClass())) {
if (((Integer) obj) > 0) {
return (Serializable) obj;
}
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return super.generate(session, object);
}
}
You need a running transaction.
In case your transaction are manually-managed:
entityManager.getTransaction().begin();
(of course don't forget to commit)
If you are using declarative transactions, use the appropriate declaration (via annotations, most likely)
Also, set the hibernate logging level to debug (log4j.logger.org.hibernate=debug) in your log4j.properties in order to trace what is happening in more details.

Categories