JPA/Hibernate Persist cascades even if it shouldn't - java

I got these 2 entities:
#javax.persistence.Entity
public class Book {
#javax.persistence.EmbeddedId
private BookPK id;
private String title;
#javax.persistence.ManyToOne(fetch = javax.persistence.FetchType.LAZY)
#javax.persistence.JoinColumns({
#javax.persistence.JoinColumn(name = "LNGCOD", referencedColumnName = "LNGCOD"),
#javax.persistence.JoinColumn(name = "LIBCOD", referencedColumnName = "LIBCOD") })
private Language language;
}
#javax.persistence.Entity
public class Language {
#javax.persistence.EmbeddedId
private LanguagePK id;
private String name;
}
with composed PK's:
#Embeddable
public class BookPK implements Serializable {
private Integer bookcod;
private Integer libcod;
}
#Embeddable
public class LanguagePK implements Serializable {
private Integer lngcod;
private Integer libcod;
}
If I try to create a new Book and persist it, I get an exception telling me libcod is found twice in the insert statement ("Column 'libcod' specified twice"). But I can't use "insertable = false" when defining the JoinColumn ("Mixing insertable and non insertable columns in a property is not allowed").
Is there any way to define these objects + relationship so the columns are managed automatically by Hibernate ?

Hibernate and JPA automatically make persistent all the modifications made to persistent entities while they are attached to the session. That's the whole point of an ORM: you load a persistent object, modify it, and the new state is automatically persisted at the commit of the transaction, without any need to call persist, merge, save or any other method.
Note that calling persist on a persistent entities (except for its cascading side-effects) makes no sense. persist is to make a transient entity (i.e. a new one, not in the database yet, with no generated ID) persistent.

You can only have one mutator for the libcod. Probably, what you should do is leave the libcod getter in the BookPK class, and in the Language class, use a joincolumn reference with a ref back to the libcod. It works fine for single embedded PK classes, but for multiple PK classes, you may have to play around.
So, in your Language class, you would have this.
#javax.persistence.Entity public class Language {
private LanguagePK id;
private Integer libcod;
#javax.persistence.EmbeddedId #AttributeOverrides({
#AttributeOverride(name = "lngcod", column = #Column(name = "LNGCOD", nullable = false)),
#AttributeOverride(name = "libcod", column = #Column(name = "LIBCOD", nullable = false)) })
public LanguagePK getId() {
return this.id;
}
public void setId(LanguagePK id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "LIBCOD", insertable = false , updatable = false)
public Integer getLibcod() {
return this.libcod;
}

Related

How to use graphql with jpa if schema is different to database structure

There is a given database structure and graphql schema.
Fortunately they have a lot in common but unfortunately there are some difference.
Let's say there are entities in java to match the following database structure.
SQL:
TABLE ANIMAL
+ID NUMBER(19)
+NR_OF_LEGS NUMBER(19)
TABLE SHEEP
+ID NUMBER
+LAST_TIME_SHEARED DATETIME
+ANIMAL_ID NUMBER(19)
TABLE COW
+MILK_IN_L NUMBER(3)
+ANIMAL_ID NUMER(19)
Java:
#Entity
#Table(name = "ANIMAL")
public class Animal
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name="nrOfLegs", nullable=false)
private long nrOfLegs;
}
#Entity
#Table(name = "SHEEP")
public class SheepE
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name="lastTimeSheared", nullable=false)
private Datetime lastTimeSheared;
#ManyToOne(targetEntity = AnimalE.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "animalId", nullable = false, insertable = false, updatable = false)
private Animal animal;
}
#Entity
#Table(name = "COW")
public class CowE
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name="milkInL", nullable=false)
private int milkInL;
#ManyToOne(targetEntity = AnimalE.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "animalId", nullable = false, insertable = false, updatable = false)
private Animal animal;
}
The existing GraphQl schema is considered to be like this:
type Sheep{
id: int!
lastTimeSheard: String!
nrOfLegs: int!
}
type Cow {
id: int!
milkInL: int!
nrOfLegs: int
}
The project uses graphql-java in version 11.0 (guess we should update soon)
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>11.0</version>
</dependency>
The graphql works fine and isimplemented like this:
#Component
public class GraphQLProvider {
#Autowired
GraphQLDataFetchers graphQLDataFetchers;
private GraphQL graphQL;
#PostConstruct
public void init() {this.graphQL = /*init;*/null;}
private RuntimeWiring buildWiring() {
RuntimeWiring.Builder b = RuntimeWiring.newRuntimeWiring()
.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("freightCarrier", graphQLDataFetchers.getCow()))
.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("personCarrier", graphQLDataFetchers.getSheep())));
return b.build();
}
}
#Component
public class GraphQLDataFetchers {
#AutoWired
private CowRepository cowRepo;
#AutoWired
private sheepRepository sheepRepo;
public DataFetcher getCow() {
DataFetcher dataFetcher = (DataFetchingEnvironment dfe) -> {
int id = dfe.getArgument("id");
return getGraphQlCowFromCowEntity(cowRepo.getById(id));//dirty!
};
return dataFetcher;
}
public DataFetcher getCow() {
DataFetcher dataFetcher = (DataFetchingEnvironment dfe) -> {
int id = dfe.getArgument("id");
return getGraphQlSheepFromSheepEntity(cowRepo.getById(id));//dirty!
};
return dataFetcher;
}
private Cow getGraphQlCowFromCowEntity(CowE ce){//dirty!
return new Cow(ce.getId(), ce.getMilkInL(),ce.getLegs());
}
private Sheep getGraphQlSheepFromSheepEntity(SheepE se){//dirty!
return new Sheep(se.getId(), se.getLastTime(),se.getLegs());
}
public class Sheep
private long id;
private Datetime lastTimeSheared;
private int nrOfLegs;
public Sheep(long id, DateTime lasttimeSheared, int nrOfLegs){
//u know what happens here
}
}
public class Cow
private long id;
private int milkInL;
private int nrOfLegs;
public Sheep(long id, int milkInL, int nrOfLegs){
//u know what happens here
}
}
So how to get rid of getGraphQlCowFromCowEntity and getGraphQlSheepFromSheepEntity. It double ups the code and also is in direct conflict to what graphql is suppose to be abstraction of the data. With this design here each time all fields are loaded through jpa and not only requested fields.
Imagine this is a way more complex environment with more fields.
The graphql schema can't be changed as it's not my responsibility, changing the entire back-end to match schema is also not what I want to archive.
Kind regards
You should use DTO. Retrieving and sending entity object is bad practice as you do not want your grahql api to change every time you refactor you database model, or in your case. Your Sheep and Cow objects are DTO, but you will need some way to convert your entity to DTO (getGraphQlCowFromCowEntity is fine, but you could use polymorphism - CowEntity.toDTO() - or have a service layer do the conversion, there are plenty of way to do this).
To answer your concerns about loading only the requested data, you want your DTO object to only be populated with the requested fields. One way to do this is, instead of populating all fields, have the DTO own a reference to the entity object and retrieve the data from the entity object only when requested.
public class Sheep {
private SheepE entity;
public Sheep(SheepE entity){
this.entity=entity;
}
public getId() {
return entity.getId();
}
public getLastTimeSheared() {
return entity.getLastTimeSheared();
}
...
}
Please see this answer I wrote to a similar question: Graphql Tools: Map entity type to graphql type

Dropwizard-Hibernate Lazy fetch is not lazy

I am using Dropwizard-1.1.2 with hibernate-5.2.8. I implemented one-to-many relationship like this:
Parent Table:
#TypeDefs( {#TypeDef( name= "StringJsonObject", typeClass = StringJsonUserType.class)})
#Table(name="parent_table")
#Entity
public class ParentTable {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(
name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator"
)
#Column(name = "parent_id", updatable = false, nullable = false)
private UUID id;
#JoinColumn(name="parent_id")
#OneToMany(targetEntity = NotificationModel.class, cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
private List<NotificationModel> notifications = new ArrayList<>();
public UUID getId() {
return id;
}
public List<NotificationModel> getNotifications() {
return notifications;
}
public void setNotifications(List<NotificationModel> notifications) {
this.notifications = notifications;
}
}
Notification Table
#Table(name="notifications")
#Entity
public class ReminderNotificationModel {
#Id
#GeneratedValue
private Integer id;
#Column(name = "notification_id")
#Type(type="pg-uuid")
private UUID notificationId;
private String message;
#Column(name = "notification_status")
private String notificationStatus;
#Column(name = "scheduled_at")
private DateTime scheduledAt;
// getters and constructors
}
Now in my DAO no matter if I try native-query, criteria query or get by id on parent table, it always gives me all the notifications as well. Should the notifications be fetched lazily?
DAO
public class ReminderDao extends AbstractDAO<ReminderModel> {
#Inject
public ReminderDao(SessionFactory sessionFactory) {
super(sessionFactory);
}
public ReminderModel getById(UUID id) {
ReminderModel m = get(id); // m has notifications as well
return m;
}
}
I was reading hibernate's documentation which says Lazy is a hint for basic types. However collection is not a basic type. So lazy should have been honored. What am I missing?
LAZY is merely a hint that the value be fetched when the attribute is
accessed. Hibernate ignores this setting for basic types unless you
are using bytecode enhancement
"Loading the notifications of a parent lazily" is not the same thing as "not loading notifications of a parent and pretend the parent has no notification at all".
Loading lazily means that the notifications are loaded only when the code reads them from the list, for example when printing them with the debugger, or when serializing them to JSON, or when getting the size of the list.
If you really want to test that lazy loading works correctly, then load a parent, end the transaction and close the entity manager, and check that getting the size of the list throws an exception. Or load a parent, then detach it, then check that getting the size of the list throws an exception. Or load the parent, and check that Hibernate.isInitialized(parent.getNotifications()) is false.

Is it possible to still use Hibernate criteria, if changing the hbm.xml files by removing entire mapping relationships?

I've been working on a project which is now in production mode. Now I've been told to remove the mappings completely from the .hbm.xml files so that I need to handle every relationships manually in the program code. It's really a big problem coz the every DB operation which I've written is in Hibernate Criteria.
Just consider the following Criteria
Criteria criteria = getSession().createCriteria(Table1.class,"table1");
criteria.createAlias("table1.table2", "table2")
.createAlias("table1.table3", "table3")
.createAlias("table3.table4", "table4")
.createAlias("table3.table5", "table5")
.setProjection(Projections.projectionList()
.add(Projections.property("id"),"id")
.add(Projections.property("c1"),"c1")
.add(Projections.property("c2"),"c2")
.add(Projections.property("c3"),"c3")
.add(Projections.property("table2.c1"),"table2.c1")
.add(Projections.property("table2.c2"),"table2.c2")
.add(Projections.property("table3.c1"),"table3.c1")
.add(Projections.property("table5.c1"),"table3.table5.c1"))
.add(Restrictions.eq("table4.c1", Constants.STATUS_ENABLED))
.setResultTransformer(new AliasToBeanNestedResultTransformer(Table1.class));
return criteria.list();
This is the criteria which is written when all the relationships are present in .hbm.xml files. Now you can understand what will be problem I'm going to face when removing the mapping from .hbm.xml files. TBH, I've to rework entire DAO classes by removing Criteria and replacing it with HQL. Also I'll not be able to fetch the result directly as object by using HQL.
Is it possible to only make small changes to the criteria (like defining the join between tables in the criteria itself) so that I'll get the same output even after removing the mappings from .hbm.xml files..?
Yes you can use the Java Persistence Annotations in the Entity classes and will work the same way the .hbm.xml classes do.
Take this for example
#Entity
public class Employee {
#SequenceGenerator(name="EMPLOYEE_SEQ", sequenceName="EMPLOYEE_SEQ", initialValue=1, allocationSize=1)
#Id #GeneratedValue(strategy=GenerationType.AUTO, generator="EMPLOYEE_SEQ")
private int id;
#Column(nullable = false, length = 50)
private String name;
#ManyToOne(targetEntity = Country.class, optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "loanID", updatable = false, insertable = false)
private Loan loan;
#Column(name = "loanID", updatable = true, insertable = true)
private Integer loanID;
public int getId() {
return id;
}
public void setCompanyID(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getLoanD() {
return loanID;
}
public void getLoanD(Integer loanID) {
this.loanID = loanID;
}
}
And then you just use the criterias like you used to.

JPA 2 Hibernate mapping with composite key in primary key using #IdClass with 3 tier structure

This question is very similar to: JPA (Hibernate, EclipseLink) mapping: why doesn't this code work (chain of 2 relationships using JPA 2.0, #EmbeddedId composite PK-FK)?
Actually my only (from meaningful that I spotted) difference is that I use #IdClass and that I most probably won't be able to switch to a different provider than hibernate.
but anyway here is the code (removed parts that where unimportant):
PermissionContextType.java:
#Entity
#IdClass(PermissionContextTypePk.class)
public class PermissionContextType{
#Id
private String id;
#Id
#JoinColumn (name = "PROJECT", referencedColumnName = "ID")
#ManyToOne ()
private Project project;
public static class PermissionContextTypePk implements Serializable{
public String project;
public String id;
// ... eq and hashCode here ...
}
}
PermissionContext.java:
#Entity
#IdClass(PermissionContextPk.class)
public class PermissionContext{
#Id
private String id;
#Id
#JoinColumns ({
#JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT"),
#JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "ID")
})
#ManyToOne
private PermissionContextType permissionContextType;
public static class PermissionContextPk implements Serializable{
public String id;
public PermissionContextTypePk permissionContextType;
// ... eq and hashCode here ...
}
}
Permission.java:
#Entity
#IdClass(PermissionPk.class)
public class Permission{
#Id
private String id;
#Id
#JoinColumns ({
#JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT"),
#JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "PERMISSIONCONTEXTTYPE"),
#JoinColumn (name = "PERMISSIONCONTEXT", referencedColumnName = "ID")
})
#ManyToOne
private PermissionContext permissionContext;
public static class PermissionPk implements Serializable{
public String id;
public PermissionContextPk permissionContext;
// ... eq and hashCode here ...
}
}
and what I get is:
org.hibernate.AssertionFailure: Unexpected nested component on the referenced entity when mapping a #MapsId: PermissionContext
Caused by: org.hibernate.AssertionFailure: org.hibernate.AssertionFailure: Unexpected nested component on the referenced entity when mapping a #MapsId: PermissionContext
does anybody know if this is a hibernate bug and I should post it on their issue tracking system (and pray that I would be able to update to given hibernate version) or is there something fundamentally wrong with my way of binding the entities?
I've checked it with the hibernate implementation on EAP 6.1 (4.2.0) as well as on wildfly (don't really know which one.)
Ok, so this is what I found so far :
Thanks fr my friend : https://hibernate.atlassian.net/browse/HHH-5764 which most probably is the reason for this behaviour.
And I found a workaround :
Permission.java:
#Entity
#IdClass(PermissionPk.class)
public class Permission{
#Id
private String id;
// for the next 3 fields there are no public acessors, so the public API of the class was not changed !
#Id
#Column(name = "PROJECT")
private String projectId;
#Id
#Column(name = "PERMISSIONCONTEXTTYPE")
private String permissionContextTypeId;
#Id
#Column(name = "PERMISSIONCONTEXT")
private String permissionContextId;
#JoinColumns ({
#JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT", updatable = false, insertable = false),
#JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "PERMISSIONCONTEXTTYPE", updatable = false, insertable = false),
#JoinColumn (name = "PERMISSIONCONTEXT", referencedColumnName = "ID", updatable = false, insertable = false)
})
#ManyToOne
private PermissionContext permissionContext;
public static class PermissionPk implements Serializable{
// previously they where private as well, but removed public constructor for the sake of simplicity of the question - so no changes where necesary in public API of the class !
private String id;
private String projectId;
private String permissionContextTypeId;
private String permissionContextId;
public PermissionPk () {}
public PermissionPk (String aId, PermissionContextPk aPermissionContext) {
this.id = aId;
permissionContextId = aPermissionContext.id;
permissionContextTypeId = aPermissionContext.permissionContextType.id;
projectId = aPermissionContext.permissionContextType.project;
}
... eq and hashCode here ...
}
}
The good thing about this workaround is that it does not change the public API of the class in any way
(the only change was that I needed to make fields in Pk's of context and contexttype visible to the PermissionPk - they where private before with only a public constructor [but again simplified for the question]), nor did it change the jpql queries, and at the same time workaround is scalable (to any tier amount - as long as every even pk does not contain another pk), so if the bug will be resolved it will be easy to remove the workaround.
I would still gladly accept any comments on either my workaround or the question in itself.
Today I found another workaround :)
You can omit #IdClass entirely and use hibernate specific ability to create composite keys on the fly as apparently it is not affected by this bug.
The drawback here is that:
it is entirely Hibernate specific not covered by JPA at all.
you cannot do em.find(ClassName.class,new ClassPk(args...)) as there is no ClassPk at all.
But if you could use anything else than hibernate you could just as well use something without this bug - so probably 1 is not a problem really. and there is a possibility that you don't really need the em.find for this entity (or can live with creating it thru session or jpql query).

JPA - Using composite PK's and FK's and defining relationships

I got these 2 entities:
#javax.persistence.Entity
public class Book {
#javax.persistence.EmbeddedId
private BookPK id;
private String title;
#javax.persistence.ManyToOne(fetch = javax.persistence.FetchType.LAZY)
#javax.persistence.JoinColumns({
#javax.persistence.JoinColumn(name = "LNGCOD", referencedColumnName = "LNGCOD"),
#javax.persistence.JoinColumn(name = "LIBCOD", referencedColumnName = "LIBCOD") })
private Language language;
}
#javax.persistence.Entity
public class Language {
#javax.persistence.EmbeddedId
private LanguagePK id;
private String name;
}
with composed PK's:
#Embeddable
public class BookPK implements Serializable {
private Integer bookcod;
private Integer libcod;
}
#Embeddable
public class LanguagePK implements Serializable {
private Integer lngcod;
private Integer libcod;
}
If I try to create a new Book and persist it, I get an exception telling me libcod is found twice in the insert statement ("Column 'libcod' specified twice"). But I can't use "insertable = false" when defining the JoinColumn ("Mixing insertable and non insertable columns in a property is not allowed").
Is there any way to define these objects + relationship so the columns are managed automatically by Hibernate ? (I am especially thinking of libcod).
Thank you.
Create a third property "Integer libcod;" on the Book. Have that property manage the db state of libcod. Use insertable=false,updatable=false for both properties in the join to Language. in your "setLanguage" set the private libcod = language.libcod. don't expose a getter/setter for the private libcod.
Are any of the values generated at insert time? This could complicate things further, I suppose.

Categories

Resources