I have an entity with reference to another one by FK, at the same time I have a field mapped on the same column to have access right to the identifier, let's say
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue
Long id;
#JoinColumn(name = "author_id")
#ManyToOne(fetch = FetchType.LAZY)
Author author;
#Column(name = "author_id", insertable = false, updatable = false)
Long authorId;
}
so, for now on select via JPA repository (findById for instance) the field of "authorId" is always null, but in actual database it exists and object of "author" fills correctly. Tested in the transaction and outside - result is the same.
About app - it is spring boot 2.2.8 with spring data
Are there any ideas where I can be wrong?
*Update: found the reason - all the found entities are cached somehow, after detaching them from persistence context all data loads as expected. Seems it's clear, but still cant get where interactions with these entities appear, obviously not in my tx - it is pretty small and simple. Never thought that neighboring transactions can affect cache this way =((
Related
I am facing lazy inizialization issue when I added Lombok project into my hibernate project and used its #Getter and #Setter on the entity class.
Entity classes are annotated with #Entity of Javax.persistence as I am using hibernate 5.
Issue stacktrace :-
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:146)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:259)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:73)
at com.capehenry.domain.user.User_$$_jvst52e_9.getId(User_$$_jvst52e_9.java)
at com.capehenry.business.rs.course.SeatRequestResource.validateSeatRequestCancel(SeatRequestResource.java:338)
at com.capehenry.business.rs.course.SeatRequestResource.cancel(SeatRequestResource.java:220)
Everything was working fine with below code
#Entity
#Audited
#Table(name = "seat_request")
public class SeatRequest extends BaseEntity {
private CourseSchedule courseSchedule;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "courseScheduleId", nullable = false)
public CourseSchedule getCourseSchedule() {
return courseSchedule;
}
public void setCourseSchedule(CourseSchedule courseSchedule) {
this.courseSchedule = courseSchedule;
}
When I do searRequest.getCourseSchedule().getId() it works at rest layer means outside the transaction.
As soon as I change the code to below (add lombok), searRequest.getCourseSchedule().getId() at rest layer starts throwing lazyInitializationException :-
#Entity
#Audited
#Table(name = "seat_request")
#Setter
public class SeatRequest extends BaseEntity {
#ManyToOne(fetch = FetchType.LAZY, optional=false)
#JoinColumn(name = "courseScheduleId", nullable = false)
private CourseSchedule courseSchedule;
NOTE :-
1) I have to compulsory use Lombok project
2) I have to use searRequest.getCourseSchedule().getId() outside Sevrice and trasaction
Please suggest the solution, Thanks in advance!!
I have to use searRequest.getCourseSchedule().getId() outside Service and transaction
I have noticed this just now... If you are outside the service and transaction you will always have that exception. Try to use FetchType.EAGER and it should work.
When you are out of transaction your entities are detached, this means that all collections that you marked as lazy won't be loaded. So you have two options: the first is to perform all calls to collections getters inside the transaction, the second one is to mark as eager your collection, so when Hibernate loads the entity it will also load referenced collection immediately. Alternatively you could map to a DTO your Entity inside your transaction. As long as you are in the transaction the getters of lazy loaded field will always work, so a mapper to the DTO would access all informations. Once that the DTO is out of the transaction you will have access to all fields you have mapped, and than do whatever you want.
Here is how I solved the issue finally!
I thought the issue started after integration with Lombok project but the issue started when the annotations were moved to field level from method (property) level.
Bear with me for the long answer.
Here foreign is refering to the database level foreign tables.
To access any column from foreign table's outside the transaction you need to either use FetchType.Eager (which is default in hibernate for any foreign object) or need to join/subquery that table.
But if you just want to fetch the foreign key(column) with which 2 tables are joined (in our case the ID) and want to keep FetchType.LAZY then you can do it in 2 ways :-
1) Keep annotations (manyToOne, JoinColumn etc) on getter methods
2) If annotations has to be kept on field level then write one more annotation on foreign key field in parent table which is - #Access(AccessType.PROPERTY)
So in above code to solve I added this annotation on id field of course Schedule
#Entity
#Audited
#Table(name = "course_schedule")
#Getter
#Setter
public class CourseSchedule{
#Id
#GenericGenerator(name = "autoincr", strategy = "native")
#GeneratedValue(generator = "autoincr")
#Column(name = "id", unique = true, nullable = false)
#Access(AccessType.PROPERTY)
protected Long id;
..........
}
So no change was required in Seat Request.
I have defined roles in my database role_template.
#Entity
#Table(name = "role_template")
#Cacheable
public class Role {
#Id
private int id;
private String name;
#Transient
private final int identity = new Random().nextInt(1000000) + 1;
}
I have one role at this moment with id=1 and name="admin"
My entity User has a list of roles defined as follow
#Entity
#Table(name = "app_user")
public class User {
[...]
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "role_assign",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"))
private Set<Role> roles;
}
Roles are joined to users with my association table
[Table `role_assign`]
int user_id
int role_id
My problem is predictable, #Cacheable does not work.
I tried with 2 users, they have the same Role template, but not the same instance. The transient variable identity isn't equals for the role of the two users.. My app configuration is good, I think I forgot something to make it working for #JoinTable
Is this the javax.persistence.Cacheable annotation? Because it should.
I think your understanding of how caching works with JPA is wrong and your observations is not sufficient to decide if caching takes place or not.
#Cacheable is about the 2nd level cache. If an entity is pulled from the cache it is instantiated from information stored in the cache, and not actually the same instance. The latter wouldn't work. Entities can always only be attached to a single session, but the 2nd level cache lives across sessions.
Two representations of an entity should be the same instance exactly if they belong to the same session.
In order to decide if the cache is used or not you have two good options:
Log the SQL statements issued against the database and see if the data for the entity is selected over and over again, or only once.
Log the cache interaction and see what is going on directly.
How you do that depends on the JPA provider you use. Here are instructions for Hibernate.
I'm mapping a database (Oracle 11g) to JPA entities using EclipseLink. I have mapped almost every table to JPA objects but I've just found a problem:
In the \Curve\ entity I have the following fields:
#Id
#Column(name = "COD_CURVE")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq.gen")
#SequenceGenerator(name = "seq.gen.curve", sequenceName = "SEQCURVE", allocationSize = 1)
private long codCurve;
#Id
#Column(name = "FEC_HISTORIC")
#Temporal(javax.persistence.TemporalType.DATE)
private Date fecHistoric;
#OneToMany(mappedBy="codCurve", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Collection<CurveDetail> detailsCollection;
In the \CurveDetail\ entity I have this:
#ManyToOne
#JoinColumns({
#JoinColumn(name = "COD_CURVE", referencedColumnName = "COD_CURVE"),
#JoinColumn(name = "FEC_HISTORIC", referencedColumnName = "FEC_HISTORIC")
})
private Curve codCurve;
The problem is that when I query the \Curve\ entity, the details always are null, despite the fact that there is valid data in both tables. Checking the database I've noticed that there are no foreign key constraints in the \CurveDetail\ table, so I wonder ¿Are these constraints required to map the database correctly? I haven't tried to add the FK constraint myself because I'm not allowed to (have to ask a DBA to do it, and it'll take a week).
Thanks in advance!
Having a foreign key is not required.
Check the SQL that is generated by enabling logging ("eclipselink.logging.level"="finest")
Try executing the same SQL with the same database to see if the data exists.
Also ensure you are not corrupting the shared cache by inserting/updating an object with a null collection. You must maintain both sides of a bi-directional relationship. You could try disabling the cache to see if this is what you are doing.
As I know having a FK constraint is not mandatory. Once I had the same problem (but it was Oracle 9i) and found out that the name of Entity class should match the name of the table you want to map, and also JPA SQL and Hibernate HQL are both case sensitive so be careful in writing #Column tag.
Having the following entity:
#Entity
class Transaction implements Serializable {
#Id #GeneratedValue
Long id
#Column(nullable = false, updatable = false)
Long trNumber
#OneToMany(mappedBy = "transaction", cascade = CascadeType.ALL, orphanRemoval = true)
Set<Entry> entries = new HashSet()
#Column(updatable = false, nullable = false)
#Temporal(TemporalType.TIMESTAMP)
Date creationDate
}
I'd like entries to be impossible to update but #OneToMany doesn't allow the updatable = false attribute.
Here is the Entry entity:
#Entity
class Entry implements Serializable {
#Id #GeneratedValue
Long id
#ManyToOne(cascade= CascadeType.ALL)
#JoinColumn(updatable = false, nullable = false)
Transaction transaction
}
I can use a JPA listener to throw an exception every time we try to update the Transaction but I'd like to set the updatable=false behavior at the entity level, as I do for the other attributes.
I guess that you can't achieve it using annotations in JPA 2.0. I've read in "Pro JPA 2" book from Apress that such read-only relationships are discussed for future versions of the specification.
I've had a similar problem which described here. I've also asked the Expert Group for any comment here (at the time of writing this post - no answer was given).
The cascading doesn't change a bit as you don't have to "merge" objects in order to change its state. You just can fetch the Entry which is moved to the managed state (it also makes its Transaction to be moved to the managed state), so each update must be reflected, at the end, in the database.
The #JoinColumn(updatable=false) seems to mean just that you cannot change the Transaction entity to something else - not that you cannot change its state.
You could detach the Transaction entity as soon as you fetch the Entry. In this case changes made to Transaction will be saved (it's managed) but to Entry won't (it's detached). You'd just need to remember to refresh the state of your Entry at the end, as your in-memory representation could be not equal to the database one.
I have two tables: t_promo_program and t_promo_program_param.
They are represented by the following JPA entities:
#Entity
#Table(name = "t_promo_program")
public class PromoProgram {
#Id
#Column(name = "promo_program_id")
private Long id;
#OneToMany(cascade = {CascadeType.REMOVE})
#JoinColumn(name = "promo_program_id")
private List<PromoProgramParam> params;
}
#Entity
#Table(name = "t_promo_program_param")
public class PromoProgramParam {
#Id
#Column(name = "promo_program_param_id")
private Long id;
//#NotNull // This is a Hibernate annotation so that my test db gets created with the NOT NULL attribute, I'm not married to this annotation.
#ManyToOne
#JoinColumn(name = "PROMO_PROGRAM_ID", referencedColumnName = "promo_program_id")
private PromoProgram promoProgram;
}
When I delete a PromoProgram, Hibernate hits my database with:
update
T_PROMO_PROGRAM_PARAM
set
promo_program_id=null
where
promo_program_id=?
delete
from
t_promo_program
where
promo_program_id=?
and last_change=?
I'm at a loss for where to start looking for the source of the problem.
Oh crud, it was a missing "mappedBy" field in PromoProgram.
Double-check whether you're maintaining bidirectional association consistency. That is; make sure that all PromoProgramParam entities that link to a PromoProgram as its parent are also contained in said parent's params list. It's a good idea to make sure this happens regardless of which side "initiates" the association if you will; if setPromoProgram is called on a PromoProgramParam, have the setter automatically add itself to the PromoProgram's params list. Vice versa, when calling addPromoProgramParam on a PromoProgram, have it set itself as the param's parent.
I've encountered this problem before as well, and it was due to not maintaining bidirectional consistency. I debugged around into Hibernate and found that it was unable to cascade the delete operation to the children because they weren't in the list. However, they most certainly were present in the database, and caused FK exceptions as Hibernate tried to delete only the parent without first deleting its children (which you've likely also encountered with the #NonNull in place).
FYI, I believe the proper "EJB 3.0"-way of making the PromoProgramParam.promoProgram field (say that a 100 times) non-nullable is to set the optional=false attribute on the #ManyToOne annotation.