Hibernate jpa does honour AttributeConveter - java

I have defined an attribute converter like this:
#Converter(autoApply = true)
public class MyConverter implements AttributeConverter<MyType, String> {
#Override
#Nullable
public String convertToDatabaseColumn(#Nullable final MyType attribute) {
LOG.log(Level.INFO, "Converting '{'{0}'}' to DB column", attribute);
return attribute != null ? map(attribute) : null;
}
#Override
#Nullable
public MyType convertToEntityAttribute(#Nullable final String dbData) {
LOG.log(Level.INFO, "Converting '{'{0}'}' to type", dbData);
return dbData != null ? map(dbData) : null;
}
}
Then in my entity class:
#Entity
public class MyEntity implements Serializable {
#Id
private Long id;
private MyType myData;
}
According to jpa2.1 specs, i do not have to annotate the attribute field with #Convert if i have specified autoApply on the converter.
Nonetheless, even if i do not specify the autoApply on the converter and specify the following:
#Entity
public class MyEntity implements Serializable {
#Id
private Long id;
#Convert(converter = MyConverter.class)
private MyType myData;
}
Hibernate still does not consider this converter.
What could i be doing wrong?
I have deleted the table and regenerated it, but nothing does help.
I have tried hibernate versions from 4.3.4 - 4.3.8 with no success, n wildfly 8.1
As aside note, My converter is declared in an entity-jar, which is then included in ejb-jar as a dependency.

Well.
After several hours, the solution seems to be simple.
My mistake was that i declared classes in the ejb-jars persistence.xml, instead of specifying the jar-file element. Therefore, the jpa hibernate annotation engine had no idea of my entity-jar, and could not scan it for the Converter annotation

Related

How should we handle id on Hibernate 6 on Sql server

We are migrating from Hibernate 5 to 6, and are having issues with the conversion of our id.
#MappedSuperclass
public abstract class Entity {
#Id
#Column(name = "ID")
#Convert(converter = UUIDConverter.class)
private UUID id = UUID.randomUUID();
}
The converter is set up like this:
#Converter
public class UUIDConverter implements AttributeConverter<UUID, String> {
#Override
public String convertToDatabaseColumn(UUID attribute) {
return attribute.toString();
}
#Override
public UUID convertToEntityAttribute(String dbData) {
return UUID.fromString(dbData);
}
}
We are using Sql Server, and the id is a uniqueidentifier in the database. If we save an object with id 8f935c03-0971-445e-9526-0ecbc743b470, this will be saved in the database as 035C938F-7109-5E44-9526-0ECBC743B470.
Any suggestion on how to solve this? What is a best-practice way to handle id`s? Some documentation say that we should not combine #Id with #Convert, but we have not found out what the alternative is.
We have tried converting to uppercase in the converter and we have tried using in IdClass.
JPA now comes with support for UUID built-in, so it should be as simple as:
#MappedSuperclass
public abstract class Entity {
#Id
#GeneratedValue
private UUID id;
}
Or, if you want some more control, check out the #UuidGenerator annotation in org.hibernate.annotations.
https://docs.jboss.org/hibernate/orm/6.2/javadocs/org/hibernate/annotations/UuidGenerator.html

How to mapping an entity to multiple mongodb collection in Spring Data MongoDB with JPA without use MongoTempalte directly?

I'm using org.mongodb:bson:4.1.2 with org.springframework.boot:spring-boot-starter-data-mongodb:2.4.7.
My entity looks like:
#Entity
#Table(name = "fire_alert")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#Document(collection = "alert_<dynamic>")
#Data
public class AlertPO implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "owner_id")
private Long ownerId;
#Column(name = "alert_type")
private Long alertType;
}
Cause there will be millions of alerts, so I need to save records into different mongodb collections based on AlertPO.alertType.
After digging into org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity, I found the field collection of annotation #Document support SpEL expression. This kind of expressions will be evaluated in org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity#getCollection and it is obviously that current entity won't be added into the EvaluationContext.
According to this question:
How to Map a Java Entity to Multiple MongoDB Collections in Spring Data?
We can overwrite repositories to use MongoTemplate to persistent data into proper collection programmatically. But we're going to using JPA and we do not want to using MongoTemplate directly. How to do this?
Although the SpEL expression's EvaluationContext contains no instances of AlertPO, but we can make it possible through ThreadLocal instances registered in BeanFactory.
Such as:
#Component(value = "collection")
public class CollectionHolder {
private static final ThreadLocal<Stack<String>> COLLECTION = ThreadLocal.withInitial(Stack::new);
public void push(Alert alert) {
COLLECTION.get().push(typeToCollection(alert.getAlertType()));
}
public void push(Long type) {
COLLECTION.get().push(typeToCollection(type));
}
public String top() {
return COLLECTION.get().peek();
}
public void pop() {
COLLECTION.get().pop();
}
}
Now we can push collection name:
#Service
public class MongoAlertStorage implements IAlertStorage {
#Autowired
private CollectionHolder holder;
#Autowired
private AlertRepository alertRepository;
#Autowired
private Converters converters;
#Override
public Alert save(Alert alert) throws Exception {
try {
holder.push(alert);
return converters.of(alertRepository.save(converters.of(alert)));
} finally {
holder.pop();
}
}
}
It is time to specify dynamic collection in expression:
#Document(collection = "alert_#{#collection.top()}")
public class AlertPO implements Serializable {
// ...
}
Now, you can debug at org.springframework.expression.common.CompositeStringExpression#getValue(org.springframework.expression.EvaluationContext) to check the value evaluated, it should be what you want.

How to map Class<?> in JPA (Hibernate)

I'm writing an application using Hibernate and I was wondering how should I map an entity defined as:
#Entity
public Class MyEntity implements Serializable {
#Id
private Long id;
#Column
private Class<?> clazz;
// getters/setters...
}
Is it possible to map this to a DB? Should I add an extra annotation to clazz?
Note that this is a curiosity, I'm not in a hurry this time, so please do not try to supply alternate solutions. Thanks in advance for your answers.
There is no JPA mapping for a Class object out of the box, but to satisfy most (if not all) use cases, you could do something like this
#Entity
public Class MyEntity implements Serializable {
#Id
private Long id;
#Column
private String clazz;
public Class getClazz () {
return this.clazz != null ? Class.forName(this.clazz) : null;
}
public Class setClazz (Class clazz) {
this.clazz = clazz.getName();
}
}
In this case, you are simply storing the name of the class as a String, and then when you retrieve it from the database, the getter will convert it into a proper class object.

Java persistence mapped superclass with optional properties

I'm using the javax.persistence package to map my Java classes.
I have entities like these:
public class UserEntity extends IdEntity {
}
which extends a mapped superclass named IdEntity:
#MappedSuperclass
public class IdEntity extends VersionEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// Getters and setters below...
}
The IdEntity super class extends another mapped super class named VersionEntity to make all entities inherit version properties:
#MappedSuperclass
public abstract class VersionEntity {
#Version
private Integer version;
// Getters and setters below...
}
Why?
Because now I can make generic queries on the IdEntity class for all entities, and it will look like this: (example)
CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<IdEntity> criteria = builder.createQuery(IdEntity.class);
Now to the problem.
Some of my entities will have timestamps like created_at and deleted_at. But not all entities.
I could provide these properties in my entity classes like this:
public class UserEntity extends IdEntity {
#Basic(optional = false)
#Column(name = "updated_at")
#Temporal(TemporalType.TIMESTAMP)
private Date updatedAt;
}
But as I have a lot of entities, this will make me put a lot of redundant code in all entities that should have timestamps. I wish there was some way I could make the relevant classes inherit these fields in some way.
One possible solution is to create a parallell IdEntity superclass, maybe named IdAndTimeStampEntity and make those entities that should have timestamps inherit from this new superclass instead, but hey that's not fair to my colleague-developers because now they have to know which super class to choose from when writing generic queries:
CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<???> criteria = builder.createQuery(???); // Hmm which entity should I choose IdEntity or IdAndTimeStampEntity ?? *Annoyed*
And the generic entity queries become not so generic..
My question: How can I make all of my entities inherit id and
version fields, but only a sub part of all entities inherit
timestamp fields, but keep my queries to a single type of entities?
Update #1
Question from Bolzano: "can you add the code which you specify the path(holds table info) for entities ?"
Here is a working example of querying a UserEntity which is a IdEntity
CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<IdEntity> criteria = builder.createQuery(IdEntity.class);
Root<IdEntity> from = criteria.from(IdEntity.class);
criteria.select(from);
Path<Integer> idPath = from.get(UserEntity_.id); //generated meta model
criteria.where(builder.in(idPath).value(id));
TypedQuery<IdEntity> query = JPA.em().createQuery(criteria);
return query.getSingleResult();
I would pick a solution that didn't enforce a class-based object model like you've outlined. What happens when you don't need optimistic concurrency checking and no timestamps, or timestamps but no OCC, or the next semi-common piece of functionality you want to add? The permutations will become unmanageable.
I would add these common interactions as interfaces, and I would enhance your reusable find by id with generics to return the actual class you care about to the caller instead of the base superclass.
Note: I wrote this code in Stack Overflow. It may need some tweaking to compile.
#MappedSuperclass
public abstract class Persistable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// getter/setter
}
public interface Versioned {
Integer getVersion();
}
public interface Timestamped {
Date getCreated();
Date getLastUpdated();
}
#Embeddable
public class TimestampedEntity {
#Column(name = "create_date")
#Temporal
private Date created;
#Column
#Temporal
private Date lastUpdated;
// getters/setters
}
#Entity
public class UserEntity extends Persistable implements Versioned, Timestamped {
#Version
private Integer version;
#Embedded
private TimestampedEntity timestamps;
/*
* interface-defined getters. getTimestamps() doesn't need to
* be exposed separately.
*/
}
public class <CriteriaHelperUtil> {
public <T extends Persistable> T getEntity(Class<T> clazz, Integer id, SingularAttribute idField) {
CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(clazz);
Root<T> from = criteria.from(clazz);
criteria.select(from);
Path<Integer> idPath = from.get(idField);
criteria.where(builder.in(idPath).value(id));
TypedQuery<T> query = JPA.em().createQuery(criteria);
return query.getSingleResult();
}
}
Basic Usage:
private UserEntity ue = CriteriaHelperUtil.getEntity(UserEntity.class, 1, UserEntity_.id);
ue.getId();
ue.getVersion();
ue.getCreated();
// FooEntity implements Persistable, Timestamped
private FooEntity fe = CriteriaHelperUtil.getEntity(FooEntity.class, 10, FooEntity_.id);
fe.getId();
fe.getCreated();
fe.getVersion(); // Compile Error!
#MappedSuperclass
public class IdEntity{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Version
private Integer version;
}
#MappedSuperclass
public class IdAndTimeStampEntity extends IdEntity{
Date created;
}
#Entity
public class UserEntity extends IdAndTimeStampEntity{
String name;
}
#Entity
public class FooEntity extends IdEntity{...
Pros of this solution:
In simple and clear way uses OOP without need to embed duplicate code implementing intefaces in every subclass. (Every class is also interface)
Optimistic locking version column is mostly used approach. And should be part of base class. Except read only entities like codetables.
Usage:
public <T extends IdEntity> T persist(T entity) {
if (entity instanceof IdAndTimeStampEntity) {
((IdAndTimeStampEntity) entity).setCreated(new Date());
}
if (!em.contains(entity) && entity.getId() != null) {
return em.merge(entity);
} else {
em.persist(entity);
return entity;
}
}
I wish there was some way I could make the relevant classes inherit these fields in some way.
You could make a custom annotation #Timed and use an annotation processor to add the timestamp field and annotations, either by using a bytecode manipulation framework or creating a delegating subclass. Or, for example if you use Lombok, create a Lombok annotation.
That way, your team members only have to remember to use the #Timed annotation when you have entities with timestamps. Whether you like such approach or not is up to you.

spring data jpa composite key duplicate key record insertion resulting in update

I have one entity having composite key and I am trying to persist it by using spring data jpa repository to mysql databse as given below:
#Embeddable
public class MobileVerificationKey implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name="CUSTOMERID")
private Long customerId;
#Column(name="CUSTOMERTYPE")
private Integer customerType;
#Column(name="MOBILE")
private Long mobile;
#Embeddable
public class MobileVerificationKey implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name="CUSTOMERID")
private Long customerId;
#Column(name="CUSTOMERTYPE")
private Integer customerType;
#Column(name="MOBILE")
private Long mobile;
//getter and setters
}
And Entity as
#Entity
#Table(name="mobileverificationdetails")
public class MobileVerificationDetails {
#EmbeddedId
private MobileVerificationKey key;
#Column(name="MOBILETYPE")
private String mobileType;
#Column(name="MOBILEPIN")
private Integer mobilePin;
//getters and setters
}
My spring data jpa repository look like this:
public interface MobileVerificationDetailsRepository extends
CrudRepository<MobileVerificationDetails, MobileVerificationKey> {
#Override
MobileVerificationDetails save(MobileVerificationDetails mobileVerificationDetails);
#Override
MobileVerificationDetails findOne(MobileVerificationKey id);
}
Now if I am trying to add duplicate record with same key for original record and different values for other fields .when i try to insert second record it results in update of existing record with new values instead of throwing exception for violating primary key constraint...can any one please explain me this behavior.
The easiest (and least invasive) way to work around this is probably by making sure the id only gets set right before the persist. This can be achieved in a #PrePersist callback:
abstract class MobileVerificationDetails {
#EmbeddedId
private MobileVerificationKey id;
#PrePersist
void initIdentifier() {
if (id == null) {
this.id = … // Create ID instance here.
}
}
}
Alternatively to that you can enforce persist(…) being used by implementing Persistable and implementing isNew() accordingly. Make sure this method returns true on first insert. We usually see people holding a transient boolean flag that is updated in an #PostPersist/#PostLoad annotated method.
abstract class AbstractEntity<ID extends Serializable> implements Persistable<ID> {
private #Transient boolean isNew = true;
#Override
public boolean isNew() {
return isNew;
}
#PostPersist
#PostLoad
void markNotNew() {
this.isNew = false;
}
}
Spring Data Jpa Repository functionality is implemented via the SimpleJpaRepository class containing following save(..) method:
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Thus the Spring Jpa Data Repository save(...) method merges an already existing entity.
Opposed to that the naked EntityManager#persist() throws an exception if invoked with already existing entity.
The problem might be solved by adding custom behavior to Spring Data Repository/ies. The custom behavior might be added using one of the approaches as described in 1.3.1 Adding custom behavior to single repositories with example here or in 1.3.2 Adding custom behavior to all repositories with example here. In both cases the custom behavior would include a new persist() method delegating to EntityManager#persist(). Note that in approach 1.3.2. you already have a EntityManager instance, in the approach 1.3.1 you are able to inject EntityManager instance using the #PersistenceContext.
Opposed to my comment I would recommend adding new method to the repository and not overwriting the existing save(...).

Categories