JPA #GeneratedValue on id - jpa

In JPA, I am using #GeneratedValue:
#TableGenerator(name = "idGenerator", table = "generator", pkColumnName = "Indecator" , valueColumnName = "value", pkColumnValue = "man")
#Entity
#Table(name="Man")
public class Man implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "idGenerator")
#Column(name="ID")
private long id;
public void setId(Long i) {
this.id=i;
}
public Long getId(){
return id;
}
}
I initially set the ID to some arbitrary value (used as a test condition later on):
public class Sear {
public static void main(String[] args) throws Exception {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("testID");
EntityManager em = emf.createEntityManager();
Man man = new Man();
man.setId(-1L);
try {
em.getTransaction().begin();
em.persist(man);
em.getTransaction().commit();
} catch (Exception e) { }
if(man.getId() == -1);
}
}
}
What is the expected value of man.id after executing commit()? Should it be (-1), a newly generated value, or I should expect an exception?
I want to use that check to detect any exceptions while persisting.

What is the expected value of man.id after executing commit()? Should it be (-1), a newly generated value, or I should expect an exception?
You are just not supposed to set the id when using GeneratedValue. Behavior on persist will differ from one implementation to another and relying on this behavior is thus a bad idea (non portable).
I want to use that check to detect any exceptions while persisting.
JPA will throw a (subclass of) PersistenceException if a problem occurs. The right way to handle a problem would be to catch this exception (this is a RuntimeExeption by the way).
If you insist with a poor man check, don't assign the id and check if you still have the default value after persist (in your case, it would be 0L).

You setting the value of a field that is auto-generated is irrelevant. It will be (should be) set by the JPA implementation according to the strategy specified.

In EclipseLink this is configurable using the IdValidation enum and the #PrimaryKey annotation or the "eclipselink.id-validation" persistence unit property.
By default null and 0 will cause the id to be regenerated, but other values will be used. If you set the IdValidation to NEGATIVE, then negative numbers will also be replaced.
You can also configure your Sequence object to always replace the value.

Related

Kotlin inheritance and JPA

I'm trying to implement inheritance with Kotlin and JPA. My abstract base class (annotated with #Entity) holds the ID (annotated with #Id and #GeneratedValue) and other metadata, like createDate, etc. I'm getting several errors from Hibernate, one for each field except the ID:
org.hibernate.tuple.entity.PojoEntityTuplizer - HHH000112: Getters of lazy classes cannot be final: com.example.BaseEntity.createDate
As I've read I need to include the open keyword for each property.
I have 3 questions regarding this:
Why do I have to do that in the superclass, and don't need in subclass? I'm not overriding those properties.
Why isn't it complaining about the ID?
It seems to work without the open keyword, then why is the error logged?
Edit:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
abstract class BaseEntity(
#Id #GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0,
val createdAt: Instant = Instant.now()
)
#Entity
class SubClass(
val someProperty: String = ""
) : BaseEntity()
I'm using the JPA plugin for Gradle, which I believe creates the noarg constructor, that's why I don't have to specify everything nullable.
Thank you!
The logged error has to do with lazy loading.
Hibernate extends entities at runtime to enable it. It is done by intercepting an access to properties when an entity is loaded lazily.
Kotlin has flipped the rules and all classes are final by default there. It is the reason why we're advised to add an open keyword.
If a property is not open hibernate cannot intercept access to it because final methods cannot be overridden. Hence the error.
Why isn't it complaining about the ID?
Because #Id is always loaded. There is no need to intercept access to it.
It seems to work without the open keyword, then why is the error logged?
The key word here is seems. It may introduce subtle bugs.
Consider the following #Entity:
#Entity
public class Book {
#Id
private Long id;
private String title;
public final Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public final String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
And the #Test:
#Test
public void test() {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
// signal here
Book book = new Book();
book.setId(1L);
book.setTitle("myTitle");
entityManager.persist(book);
// noise
entityManager.getTransaction().commit();
entityManager.close();
entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
// signal
Book reference = entityManager.getReference(Book.class, 1L);
String title = reference.getTitle();
assertNull(title); // passes
entityManager.getTransaction().commit();
entityManager.close();
}
This test passes but it should not (and fails if getTitle is not final).
This would be hard to notice
Why do I have to do that in the superclass, and don't need in subclass? I'm not overriding those properties.
Looks like Hibernate gives up when it sees final #Entity.
Add open to SubClass and you will the precious:
2019-05-02 23:27:27.500 ERROR 5609 --- [ main] o.h.tuple.entity.PojoEntityTuplizer : HHH000112: Getters of lazy classes cannot be final: com.caco3.hibernateanswer.SubClass.someProperty
See also:
final methods on entity silently breaks lazy proxy loading
How to avoid initializing HibernateProxy when invoking toString() on it? - my old question (note that Hibernate uses Byte Buddy these days).
PS
Did you forget to include #MappedSuperclass on BaseEntity?
Without the annotation it should fail with something like:
org.hibernate.AnnotationException: No identifier specified for entity: com.caco3.hibernateanswer.SubClass

Lazy Loading with EJB + JPA + Jersey

I have the following working without FetchType.LAZY:
#Entity
public class Test {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String text;
#ManyToOne
#JoinColumn(name = "lazy_id")
private Lazy lazy;
//getters and setters
}
#Entity
public class Lazy {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String text;
//getters and setters
}
And the query method:
public List<Test> all() {
try {
return em.createQuery("FROM Test t").getResultList();
} catch (NoResultException e) {
return null;
}
}
This is the JSON result:
[{"id":1,"text":"test 1","lazy":{"id":1,"text":"lazy 1"}},
{"id":2,"text":"test 2","lazy":{"id":2,"text":"lazy 2"}}]
However I want to return just the id and text data, so I tried to change the #ManyToOne(fetch = FetchType.LAZY)
Then I get this errors:
Severe: Generating incomplete JSON
Severe: org.hibernate.LazyInitializationException: could not initialize proxy [model.Lazy#1] - no Session
I could do something like changing the query to fetch only the fields I want:
public List<Test> all() {
try {
return em.createQuery("SELECT t.id, t.text FROM Test t").getResultList();
} catch (NoResultException e) {
return null;
}
}
But then my response in the JavaScript front end is:
[[1,"test 1"],[2,"test 2"]]
Not a array of objects anymore, mapping everything giving the amount of entities I have is far from ideal.
Most of the content I found is how to fetch the data afterwards, which is not my concern, all I need is to send only fields I want in the first place. I`m not sure whether the EJB #TransactionAttribute should be used or not, I couldn't find a working example. I also tried to change the strategy to a #OneToMany in the Lazy class but to no avail.
Since your question dates back a bit, I hope it's still relevant for you:
If you declare a mapping as lazy (or it is like that by the default behaviour), JPA won't fetch it until it is accessed. So your Lazy class will only be accessed if JSON tries to convert the whole thing and at that point it seems that you no longer have an open session, so the data can't be fetched and will result in an org.hibernate.LazyInitializationException.
If you stick with a lazy mapping (which is in general mostly fine), you have to explicitely fetch or access it, if you need the data for an use case.
Check out Vlad's excellent explanation on the topic.

Check entity existence before calling merge

I have a simple abstract DAO, and I have created the following method:
protected T update(T entity) {
return em.merge(entity);
}
where the entity is just any object annotated with #Entity in my application. Now... I want to throw an exception if you try to update a non existing object. I was going to perform a find before the merge, throwing an exception if the find operation returns null and merging if the entity exists. I was wandering if a better way exists for doing this.
A possible solution: You can do a check based on your primary key. An entity must (should?) have an #Id field:
#Entity
public class Entity implements EntityInterface{
#Id
private Long id;
#Override
public Long getId(){
return this.id;
}
}
with the interface
public interface EntityInterface{
public Long getId();
}
By default, when you instantiate your entity, id is null and a value is assigned only after persisting in the database: The id will be generated by the method you defined via #GeneratedValue. Consequently, the following check should meet your requirement:
public abstract class AbstractService<T extends EntityInterface>{
protected T update(T entity){
// if by any chance you have to call this method on an entity with a null
// primary key, it means that the entity has not been persisted in the
// database yet
if(entity.getId() == null){
// or whatever
return null;
}
return em.merge(entity);
}
}
Hope this help
Source: JB Nizet's comment and personal code

Why is the same database entry represented by multiple JPA bean instances?

Today I stumbled over some unexpected behaviour of EclipseLink. (I don't know if this is bound to EclipseLink or if this is the same for all JPA providers.)
I assumed that retrievals of a managed JPA bean always return references to the same object instance when issued inside the same transaction (using the same EntityManager).
If that is right, I don't know why I receive an error when I execute the following test case:
#Test
public void test_1() {
EntityManager em = newEntityManager();
em.getTransaction().begin();
// Given:
Product prod = newProduct();
// When:
em.persist(prod);
em.flush();
Product actual =
em.createQuery("SELECT x from Product x where x.id = "
+ prod.getId(), Product.class).getSingleResult();
// Then:
assertThat(actual).isSameAs(prod); // <-- FAILS
em.getTransaction().commit();
}
The statement marked with "FAILS" throws the following AssertionError:
java.lang.AssertionError:
Expecting:
<demo.Product#35dece42>
and actual:
<demo.Product#385dfb63>
to refer to the same object
Interestingly the following slightly modified test succeeds:
#Test
public void test_2() {
EntityManager em = newEntityManager();
em.getTransaction().begin();
// Given:
Product prod = newProduct();
// When:
em.persist(prod);
em.flush();
Product actual = em.find(Product.class, prod.getId());
// Then:
assertThat(actual).isSameAs(prod); // <-- SUCCEEDS
em.getTransaction().commit();
}
Obviously there is a difference between finding and querying objects.
Is that the expected behaviour? And why?
--Edit--
I think I found the source of the problem: Product has an ID of type ProductId.
Here is the relevant code:
#Entity
#Table(name = "PRODUCT")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID", nullable = false)
#Converter(name = "productIdConverter", converterClass = ProductIdConverter.class)
#Convert("productIdConverter")
private ProductId id;
#Column(name = "NAME", nullable = false)
private String name;
[...]
}
The #Convert and #Converter annotations are EclipseLink-specific.
Unlike JPA 2.1 Converters you may place them on ID fields.
But it seems that in certain circumstances EclipseLink has problems to find a managed bean in its session cache if that bean uses a custom type for its ID field.
I guess I have to file a bug for that.
I found the cause of the problem and a solution.
We are using a custom ID class (ProductId) for Product, together with a custom (EclipseLink-specific) Converter-Class ProductIdConverter which has a bad implementation of the convertObjectValueToDataValue(...) method.
Here is the relevant code:
/**
* Convert the object's representation of the value to the databases' data representation.
*/
#Override
public final Object convertObjectValueToDataValue(Object objectValue, Session session) {
if (objectValue == null) {
return null;
}
Long longValue = ((ProductId) objectValue).getLong();
return longValue;
}
Please note that the method returns Long instances (or null).
But since we are using Oracle as our database backend and have declared the product's ID column as NUMBER, the JDBC Driver maps the column value as BigDecimal. This means, we have to make sure, that our convertObjectValueToDataValue(...) also returns BigDecimal instances.
So the correct implementation is:
/**
* Convert the object's representation of the value to the databases' data representation.
*/
#Override
public final Object convertObjectValueToDataValue(Object objectValue, Session session) {
if (objectValue == null) {
return null;
}
Long longValue = ((ProductId) objectValue).getLong();
return BigDecimal.valueOf(longValue);
}
Now this method returns only BigDecimal instances.

JPA merge readonly fields

We have the simplest CRUD task with JPA 1.0 and JAX-WS.
Let's say we have an entity Person.
#Entity
public class Person
{
#Id
private String email;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(insertable = false, updatable = false)
private ReadOnly readOnly;
#Column
private String name;
#XmlElement
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
#XmlElement
public Long getReadOnlyValue()
{
return readOnly.getValue();
}
// more get and set methods
}
Here is scenario.
Client make Web Service request to create person. On the server side everything is straightforward.
And it does work as expected.
#Stateless
#WebService
public class PersonService
{
#PersistenceContext(name = "unit-name")
private EntityManager entityManager;
public Person create(Person person)
{
entityManager.persist(person);
return person;
}
}
Now client tries to update person and this is where, as for me, JPA shows its inconsistence.
public Person update(Person person)
{
Person existingPerson = entityManager.find(Person.class, person.getEmail());
// some logic with existingPerson
// ...
// At this point existingPerson.readOnly is not null and it can't be null
// due to the database.
// The field is not updatable.
// Person object has readOnly field equal to null as it was not passed
// via SOAP request.
// And now we do merge.
entityManager.merge(person);
// At this point existingPerson.getReadOnlyValue()
// will throw NullPointerException.
// And it throws during marshalling.
// It is because now existingPerson.readOnly == person.readOnly and thus null.
// But it won't affect database anyhow because of (updatable = false)
return existingPerson;
}
To avoid this problem I need to expose set for readOnly object and do something like this before merge.
Person existingPerson = entityManager.find(Person.class, person.getEmail());
person.setReadOnlyObject(existingPerson.getReadOnlyObject()); // Arghhh!
My questions:
Is it a feature or just
inconsistence?
How do you (or would
you) handle such situations? Please
don't advice me to use DTOs.
Is it a feature or just inconsistence?
I don't know but I'd say that this is the expected behavior with merge. Here is what is happening when calling merge on a entity:
the existing entity gets loaded in the persistence context (if not already there)
the state is copied from object to merge to the loaded entity
the changes made to the loaded entity are saved to the database upon flush
the loaded entity is returned
This works fine with simple case but doesn't if you receive a partially valued object (with some fields or association set to null) to merge: the null fields will be set to null in the database, this might not be what you want.
How do you (or would you) handle such situations? Please don't advice me to use DTOs.
In that case, you should use a "manual merge": load the existing entity using find and update yourself the fields you want to update by copying the new state and let JPA detect the changes and flush them to the database.