hibernate mapping to versioned entity - java

I have entity that has a version. It has composite primary key where one part of it is id of an entity and other is version. I want to create many-to-one mapping to this entity, and I need to have the latest version on the many side of this mapping.
For example:
#Entity
#IdClass(VersionedId.class)
class SomeVersionedComponent {
private Long id;
private long version;
...
}
#Entity
class ManyMappingSide {
private Long id;
private SomeVersionedComponent component;
#ManyToOne ///????
public SomeVersionedComponent getComponent() {...}
public void setComponent(SomeVersionedComponent component) {...}
}
I do not need to save version information in ManyMapingSide, I just need to have only id, and when ManyMappingSide is loaded I want to have the latest version of SomeVersionedComponent.
How can I implement it?

This sounds very similar to the versioning supported by Hibernate Envers. You might want to look at that and incorporate it into your design instead of trying to make your own versioning scheme.

Related

Springboot JPA entity is auto loading lazy join entities

I have an entity as
#Getter
#Setter
#Entity
#Table(name = "feature")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Feature {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#OneToMany(mappedBy = "featureId", fetch = FetchType.LAZY)
private transient Collection<FeatureComponent> components;
}
While in its Repository(Dao) file, I have
public interface FeatureDao extends JpaRepository<Feature, Integer> {
#Query("SELECT e FROM Feature e")
public List<Feature> getAll();
#Query("SELECT e FROM Feature e LEFT JOIN e.components fc WHERE e.id= :id")
public Feature getWithDetail(#Param("id") Integer id);
}
When I'm calling featureDao.getAll(); it returns all features but including components list filled and because of that, my response it being too large to load on client-side.
I'm unable to understand why it is happening when I'm using Lazy fetch mode and didn't mentioned joining with components in my getAll method.
Please help to resolve that issue,
Thanks in advance.
Just like #spOOm already mentioned I also suspect this is the side effect of Jackson Feature entity serialization into JSON triggering the load of all the components.
That is why using DTOs instead of plain Entities is usually advisable when returning data via a Controller. With DTOs, you clearly define whatever you want to include in the response to the caller. You can even reorganize your model so that it fits better the needs of the clients. You decouple your inner model and the model your API consumers know, making it possible to rework your inner model and still keep the same public model. You could have the following DTO.
public class FeatureSimpleDto {
private Integer id;
private String name;
private String description;
}
Then, in your Controller or Service (here you can find different opinions) you would basically convert your Feature entity into a FeatureSimpleDto that would be returned by your Controller. Here you can use mapping libraries such as MapStruct or you can do it on your own (I usually tend to prefer doing it on my own, one less dependency to rely on).
Using Lombok may be a problem, depending on the relationship between tables, try to create getters manually in entity classes.
Thanks to everyone for providing workarounds... But every work item requires lots of changes which were not possible for me...
Luckily I found a solution that is working fine for me... Better to post here...
Step-1: Add dependency in pom.xml file
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>
Step-2: Add a 'Bean' for Hibernate Module
We can add bean in any file having #Configuration annotation... Even, we can add in Springboot main application file(where we have main method).
#Bean
public Module datatypeHibernateModule() {
return new Hibernate5Module();
}
That's it, Happy Coding...

Unable to guess FieldBridge in Search 5.5. Unindexed Fields

Versions:
Hibernate-Core: 5.2.5.Final
Hibernate-Search: 5.5.5.Final
Having the following mappings:
#Indexed
#Entity
#Table(name = "scanresult")
public class ScanResult
{
#Id
private ScanResultKey id;
#Field
#Column(name = "name")
private String name;
}
#Embeddable
public class ScanResultKey implements Serializable
{
#ManyToOne
#JoinColumn(name = "eA", referencedColumnName = "id")
private EntityA entA;
//others...
}
I have read in previous posts that this was an issue in Search 4.4 (when having composite id and foreign relations), but this should be fixed in 5.5. So apparently it is my fault. But I can't figure out what could I do wrong
Exception:
org.hibernate.search.exception.SearchException: HSEARCH000135: Unable to guess FieldBridge for id in entities.keys.ScanResultKey
Note: I only need one field(name) to be indexed
Could you please point out what I'm doing wrong?
OK, Since this question got interest near to none, according to view count, here is, briefly, the way I managed (hopefully) to resolve the problem (Please, correct me if you know more)
Verify modules' versions compatibility
According to one of the commenters in this SO question, not all (even latest) versions are compatible with each other. For example:
Hibernate Search 5.5 works with Hibernate ORM 5.0.x and 5.1.x (NOT
with 5.2.x), and with Apache Lucene 5.3.x, 5.4.x and 5.5.x (Not 6.0)
Stated by: Sanne
This is not a fix to this particular problem, but might save from other issues
Create a FieldBridge for Composite Key implementing
TwoWayFieldBridge
public class ScanResultBridge implements TwoWayFieldBridge
Add annotation to Entity Class, specifying the implementation of Bridge
#FieldBridge(impl = ScanResultBridge.class)
private ScanResultKey id;

Is it possible to force Hibernate to embed an Entity?

In my use-case, I would like to #Embedded a class C in an entity.
Another entity refers to C with #OneToMany association and therefore C is annotated with #Entity.
I am aware that this seems like bad design, yet I believe that it makes perfect sense in my case.
Is it possible to force Hibernate to embed an Entity? If I try it, Hibernate complains about a missing setter for the id property of C.
I think the problem comes from this:
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
Why not just create the entity that you want, and in that entity, embed C as well. That way you have C in both classes, one as embedded and another as embedded of the new entity.
#Embeddable
public class Contact {
private String firstname;
private String lastname;
// getters and setters removed.
}
and here is your embedding class:
#Entity
public class Student {
#Embedded
private Contact contact;
}
and here is the new entity that embeds contact also
#Entity
public class FirmContact {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int contactId;
#Embedded
private Contact contact;
}
And finally the class that insists the contact must be an entity:
#Entity
public class Business {
#OneToOne(cascade=CascadeType.ALL)
private FirmContact contacts;
}
It'll just be a couple of extra steps in java to populate the object, but it should do the mapping you want. I hope this helps.
Hibernate doesn't allow you to treat an Embeddable as an Entity or to embed an Entity. According to Hibernate types:
an Embeddable, doesn't have an identifier, since it's state is part of an owning Entity.
an Entity cannot be embedded, because each Entity has a distinct life-cycle.
Since another class already has a #OneToMany association to class C, it's obvious you cannot turn it into an Embeddable.
More, a bidirectional #OneToMany association will perform better than an embeddable collection.
What you can do, is to use it as a #OneToOne association in the entity where you wanted to embed the C entity. You can make that target entity be the owning side of the association so that the C association is bound to the target entity life-cycle.

Over ride default fetch to skip rows with certain values

I am using Java, Hibernate, Spring Data and fairly new to this technology. I need to figure out how to Skip rows that are marked as 'archived.' We have a strict guidance from our DB architect that no rows shall be deleted from the database.
#MappedSuperclass
public class AbstractEntity implements Identifiable<String> {
#Id
#GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy="uuid")
private String id;
private boolean archived; //<----?
}
#Entity
public class Employee extends AbstractEntity {
private String title;
private String fullName;
#ManyToOne
private Department dept;
}
#Entity
public class Department extends AbstractEntity {
private String name;
}
In the above example, any class extending AbstractEntity should never return rows that have archived == true. All my domain classes will be extending AbstractEntity so I'd like a solution that's either implemented in AbstractEntity.java or at some global configuration so that all generated SQL calls are 'where [table].archived <> true'
Take a look at Hibernate Filters.
#FilterDef(name="activeOnly")
#Filter(name="activeOnly", condition= "archived <> 1")
// assumes that all tables have a numeric column "archived"
// as you can notice, this is a filter at the SQL Level
// (not at the Entity level)
#MappedSuperclass
public class AbstractEntity // ....
I've never used Spring Data, but the Adding custom behavior to all repositories section of the official documentation lead me to belieave that it is quite easy to obtain an injected EntityManager and customize its behaviour. Just unwrap it and enable the filter.
Session session = entityManager.unwrap(Session.class);
session.enableFilter("activeOnly");
If you want the filter to be applied for all subclasses of the #MappedSuperclass use a recent version of Hibernate. Only version 3.5 and greater (see HHH-4332) supports this behaviour.
Also, there is one gotcha, you may need to repeat the filter on associations (See Hibernate Filters on related table with MappedSuperClass).
If you want to customize the delete operations as well, use #SQLDelete to mark archived = 1 (see Soft deletes using Hibernate annotations). But to the best of my knowledge this only works on mapped entities (nothing can be done at the #MappedSuperclass level)

Limitation of JPA 1.0 using #IdClass with *nested* composite primary keys?

Given the following example (departments - projects):
A department has the following properties (composite primary key):
#Entity
#IdClass(DeptId.class)
public class Department
{
#Id
#Column(name="number")
private Integer number;
#Id
#Column(name="country")
private String country;
#Column(name="name")
private String name;
#OneToMany(mappedBy="dept")
private Collection<Project> projects;
...
}
Here the PK class:
public class DeptId implements Serializable
{
private Integer number;
private String country;
...
}
The relationship between projects and departments is many-to-one, that is a deptartment can have many projects. The Project class is itself using a composite key referencing Department's composite key. Important note: it's only about the implementation with #IdClass not #EmbeddedId.
Then the (problematic) JPA 1.0 #IdClass implementation would have to look something like that (redundant deptNum and deptCtry properties): -> it's just a unique name within a department
#Entity
#IdClass(ProjectId.class)
public class Project
{
#Id
#Column(name="dept_number")
private Integer deptNumber;
#Id
#Column(name="dept_country")
private String deptCountry;
#Id
#Column(name="name")
private String name;
#ManyToOne
#JoinColumns({
#JoinColumn(name="dept_number", referencedColumnName="number"),
#JoinColumn(name="dept_country", referencedColumnName="country")
})
private Department dept;
...
}
The ProjectId is:
public class ProjectId implements Serializable
{
private String name;
private DeptId dept;
...
}
The problem with this is that neither Hibernate nor EclipseLink know how to map the two redundant properties deptNum and deptCtry in Project to the dept property in DeptId (or the properies within it). -> MappingException etc.
My question is:
Is this a limitation of JPA 1.0, that tables with composite keys referencing other composite keys with #IdClass implementations generally WON'T work, because the JPA implementation simply can't know how to map these fields?
As a workaround, you'd have to use #EmbeddedId for these classes or use JPA 2.0 syntax to annotate the #XToX associations with #Id. I just want to make sure my view on this is right.
Thanks
Yes, this is a limitation of JPA 1.0, corrected in JPA 2.0. In the new JPA 2.0, you can put the ID annotation on your dept relationship and completely avoid having the redundent deptCountry and deptNumber attributes, with the key class using nesting. In JPA 1.0, only basic mappings can be marked as apart of the ID, requiring redundent mappings and some code to ensure that the values/relationships get put into the cache correctly when persisting. Because of the redundancy, as mentioned in other answers, one of the mappings for a field needs to be marked read-only via the insertable/updatable=false. Doing so though means that value is not merged into the cache - so changes (such as on insert, since you can't change an objects ID once it exists) will not be reflected unless the object is refreshed from the database. If you mark the JoinColumns as read-only, you will need to get the values from the referenced dept and put them into the correspoinding basic id attributes manually when you want to persist a Project. But, you can also mark the basic attributes as read-only. Eclipselink anyway will not have any problems and will correctly set the field values using the associated dept entity (as long as it is set before persist is called on the Project). Notice though that the basic attributes may or may not be populated when you read back the project in a different context- this will depend on if the entity is refreshed from the database or not. If they are read-only, they do not get merged into the shared cache since they, being read only, should not have changed. So they can be just ignored, or if they must be populated, the entity refreshed or the values set from the dept in an event.
This same model can be reused by using the JPA2.0 #MapsId, which will also maintain the basic mappings using the values from the relationship for you. Only benifit I see is that you don't need to access the relationship (potentially causing unneccessary joins or database access on lazy relationships) to get the foreign key/id field values.
As for the ZipArea EclipseLink exceptions, they are due to ZipAreaId having a ZipId zip attribute instead it being flattened out. JPA 1.0 requires the key class to have an attribute of the same type and name for each #ID attribute in the Entity.
The problem with this is that neither Hibernate nor EclipseLink know how to map the two redundant properties deptNum and deptCtry in Project to the dept property in DeptId
This is why you need to define the ManyToOne foreign key(s) as read-only with this kind of mapping. This is done by setting the JoinColumn attributes insertable and updatable to false.
So try the following:
#Entity
#IdClass(ProjectId.class)
public class Project
{
#Id
#Column(name="dept_number")
private Integer deptNumber;
#Id
#Column(name="dept_country")
private String deptCountry;
#Id
#Column(name="name")
private String name;
#ManyToOne
#JoinColumns({
#JoinColumn(name="dept_number", referencedColumnName="number", insertable=false, updatable=false),
#JoinColumn(name="dept_country", referencedColumnName="country", insertable=false, updatable=false)
})
private Department dept;
...
}
The problem with the posted code is, that JPA 1.0 really doesn't allow nesting of composite primary key classes. This ProjectId is invalid:
public class ProjectId implements Serializable
{
private String name;
private DeptId dept;
...
}
DeptId has to be flattened, like:
public class ProjectId implements Serializable
{
private Integer deptNumber;
private String deptCountry;
private String name;
...
}
I just got an EclipseLink version to go, but Hibernate has problems with that. I wonder how to tell Hibernate that JPA 1.0 is assumed.

Categories

Resources