Join together 3 entities in Hibernate - java

I have the following entities:
Person.java
#Table(name = persons)
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "UserID", nullable = false)
private Long userId;
#Column(name = "Employeenumber", nullable = false) private String employeeNumber;
#Column(name = "Firstname", nullable = false) private String firstName;
#Column(name = "Lastname", nullable = false) private String lastName;
public User() { }
public User(String employeeNumber, String firstName, String lastName) {
super();
this.employeeNumber = employeeNumber;
this.firstName = firstName;
this.lastName = lastName;
}
/*
getter and setters
...
*/
}
Personhistory.java
#Entity
#Table(name = personhistory)
public class Personhistory {
#Id
#Column(name = "UserID", nullable = false)
private Long userId;
#Column(name = "Fromdate", nullable = false) private Date fromDate;
#Column(name = "Todate", nullable = false) private Date toDate;
#Column(name = "TeamID", nullable = false) private Integer teamId;
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "UnikId", nullable = false)
private Integer unikId;
public Userhistory() {
}
public Userhistory(Long userId, Date fromDate, Date toDate, int teamId) {
super();
this.userId = userId;
this.fromDate = fromDate;
this.toDate = toDate;
this.teamId = teamId;
}
/*
Getters and setters
...
*/
}
Team.java
#Entity
#Table(name = "team")
public class Team {
#Id
#Column(name = "TeamID")
#GeneratedValue(strategy = GenerationType.AUTO)
private int teamId;
#Column(name = "TeamNumber") private String teamNumber;
public Team() {}
public Team(String teamNumber) {
super();
this.teamNumber = teamNumber;
}
/*
Getters and setters
...
*/
}
I want to make a API call like this:
localhost:8080/users/{employee}
And get back an object containing the person (His emp-number, firstname and lastname), when he was at the team and what team that is.
If I were to write this query in MSSQL, it would look like this:
select * from persons p
join personhistory ph on ph.UserID = p.UserID
and ph.Fromdate <= cast(getdate() as date)
and ph.Todate >= cast(getdate() as date)
join team t on t.TeamID = ph.TeamID
where u.Employeenumber = '999'
I have searched around for different solutions like HQL, JPQL, Criteria and so on, but I'm unable to make it work.
Any help would be much appreciated.

AFAIK Hibernate 5.1 provides more generic joins but with prior versions you'd either have to use a cross join and add the conditions in the where-clause or provide a real relation between the entities and join on that relation (using the "with" keyword for additional join conditions).
Example (note that I left out many annotations for simplicity):
class Person {
#OneToMany( mappedBy = "user" )
Collection<Personhistory> history;
...
}
class Personhistory {
#ManyToOne
Person user;
#ManyToOne
Team team;
...
}
Then the query could become
select p, ph, t from Person p
join p.history ph with ph.fromdate <= :date and ph.toDate >= :date
join ph.team t
where p.employeeNumber = :number

Related

Hibernate: N+1 fix for #OneToOne

I have #OneToOne relationship with classes:
#Entity
#Table(name = "persons", schema = "persons_info")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "first_name",
nullable = false)
#JsonProperty("first_name")
private String firstName;
#Column(name = "last_name",
nullable = false)
#JsonProperty("last_name")
private String lastName;
#Basic
#Column(name = "birth_date", nullable = false)
private Date birthDate;
#OneToOne(cascade = CascadeType.ALL,
orphanRemoval = true)
#JoinColumn(name = "address_id",
referencedColumnName = "id")
private Address address;
// setters, getters, equals...
}
Address:
#Entity
#Table(name = "addresses", schema = "persons_info")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String city;
#Column(nullable = false)
private String street;
#Column(nullable = false)
private String house;
#Column(nullable = false)
private String flat;
#JsonIgnore
#OneToOne(mappedBy = "address")
private Person person;
// setters, getters, equals...
}
I have a default JpaRepository for person:
#Repository
public interface PersonRepository
extends JpaRepository<Person, Long> {
}
And when i calling repository.findAll() i taking n+1 problem:
Hibernate:
select
a1_0.id,
a1_0.city,
a1_0.flat,
a1_0.house,
p1_0.id,
p1_0.birth_date,
p1_0.first_name,
p1_0.last_name,
a1_0.street
from
persons_info.addresses a1_0
left join
persons_info.persons p1_0
on a1_0.id=p1_0.address_id
where
a1_0.id=?
Hibernate:
select
a1_0.id,
a1_0.city,
a1_0.flat,
a1_0.house,
p1_0.id,
p1_0.birth_date,
p1_0.first_name,
p1_0.last_name,
a1_0.street
from
persons_info.addresses a1_0
left join
persons_info.persons p1_0
on a1_0.id=p1_0.address_id
where
a1_0.id=?
How i can fix that? (I want fetching with JOIN like this example: SELECT * FROM persons INNER JOIN address ON person.address_id = address.id)
Solved by adding #Query annotation:
#Repository
public interface PersonRepository
extends JpaRepository<Person, Long> {
#Query("""
SELECT p FROM Person p
LEFT JOIN FETCH p.address a""")
public List<Person> findAll();
}
Now that's looks like:
Hibernate:
select
p1_0.id,
a1_0.id,
a1_0.city,
a1_0.flat,
a1_0.house,
a1_0.street,
p1_0.birth_date,
p1_0.first_name,
p1_0.last_name
from
persons_info.persons p1_0
left join
persons_info.addresses a1_0

How can I implement this Spring Data JPA query by method name that retrieve a specific object based on two properties?

I am working on a Spring Boot project using Spring Data JPA trying to adopt the "query by method name" style in order to define my queries into repositories.
I am finding some difficulties trying to implement a select query retrieving the list of objects based on two different "where condition". I will try to explain what I have to do.
First of all this is my main entity class named Wallet:
#Entity
#Table(name = "wallet")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Wallet implements Serializable {
private static final long serialVersionUID = 6956974379644960088L;
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name = "address")
private String address;
#Column(name = "notes")
private String notes;
#ManyToOne
#EqualsAndHashCode.Exclude // Needed by Lombock in "Many To One" relathionship to avoid error
#JoinColumn(name = "fk_user_id", referencedColumnName = "id")
#JsonBackReference(value = "user-wallets")
private User user;
#ManyToOne
#EqualsAndHashCode.Exclude // Needed by Lombock in "Many To One" relathionship to avoid error
#JoinColumn(name = "fk_coin_id", referencedColumnName = "id")
private Coin coin;
#ManyToOne
#JoinColumn(name = "type", referencedColumnName = "id")
private WalletType walletType;
public Wallet(String address, String notes, User user, Coin coin, WalletType walletType) {
super();
this.address = address;
this.notes = notes;
this.user = user;
this.coin = coin;
this.walletType = walletType;
}
}
As you can see a wallet is directly binded to a specific User object and to a specific Coin object.
For completeness this is the code of my User entity class:
#Entity
#Table(name = "portal_user")
#Getter
#Setter
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class User implements Serializable {
private static final long serialVersionUID = 5062673109048808267L;
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#Column(name = "first_name")
#NotNull(message = "{NotNull.User.firstName.Validation}")
private String firstName;
#Column(name = "middle_name")
private String middleName;
#Column(name = "surname")
#NotNull(message = "{NotNull.User.surname.Validation}")
private String surname;
#Column(name = "sex")
#NotNull(message = "{NotNull.User.sex.Validation}")
private char sex;
#Column(name = "birthdate")
#NotNull(message = "{NotNull.User.birthdate.Validation}")
private Date birthdate;
#Column(name = "tax_code")
#NotNull(message = "{NotNull.User.taxCode.Validation}")
private String taxCode;
#Column(name = "e_mail")
#NotNull(message = "{NotNull.User.email.Validation}")
private String email;
#Column(name = "pswd")
#NotNull(message = "{NotNull.User.pswd.Validation}")
private String pswd;
#Column(name = "contact_number")
#NotNull(message = "{NotNull.User.contactNumber.Validation}")
private String contactNumber;
#Temporal(TemporalType.DATE)
#Column(name = "created_at")
private Date createdAt;
#Column(name = "is_active")
private boolean is_active;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
#JsonManagedReference(value = "address")
private Set<Address> addressesList = new HashSet<>();
#ManyToMany(cascade = { CascadeType.MERGE })
#JoinTable(
name = "portal_user_user_type",
joinColumns = { #JoinColumn(name = "portal_user_id_fk") },
inverseJoinColumns = { #JoinColumn(name = "user_type_id_fk") }
)
private Set<UserType> userTypes;
#ManyToOne(fetch = FetchType.LAZY)
#JsonProperty("subagent")
private User parent;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
#JsonManagedReference(value = "user-wallets")
private Set<Wallet> wallets = new HashSet<>();
public User() {
super();
// TODO Auto-generated constructor stub
}
public User(String firstName, String middleName, String surname, char sex, Date birthdate, String taxCode,
String email, String pswd, String contactNumber, Date createdAt, boolean is_active) {
super();
this.firstName = firstName;
this.middleName = middleName;
this.surname = surname;
this.sex = sex;
this.birthdate = birthdate;
this.taxCode = taxCode;
this.email = email;
this.pswd = pswd;
this.contactNumber = contactNumber;
this.createdAt = createdAt;
this.is_active = is_active;
}
}
and this is the code of my Coin entity class:
#Entity
#Table(name = "coin")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Coin implements Serializable {
private static final long serialVersionUID = 6956974379644960088L;
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name = "name")
#NotNull(message = "{NotNull.Coin.name.Validation}")
private String name;
#Column(name = "description")
private String description;
#Column(name = "code", unique = true)
#NotNull(message = "{NotNull.Coin.code.Validation}")
private String code;
#Type(type="org.hibernate.type.BinaryType")
#Column(name = "logo")
private byte[] logo;
}
Then I have this WalletRepository interface:
public interface WalletRepository extends JpaRepository<Wallet, Integer> {
}
Here I need to define a query by name method that retrieve a specific wallet of a specific User (I think that I can query by the id field of the User) and based and related to a specific Coin (I think that I can query by the id fied of the Coin).
How can I implement a behavior like this?
The following should work:
public interface WalletRepository extends JpaRepository<Wallet, Integer> {
List<Wallet> findByUserIdAndCoinId();
}
You can read more about this at:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-keywords

JPA specification - query the top result after ordering by date

It is my first experience with JPA-Specification.
I tried to implement a sample project with same requirements of my real project.
Here Are my Entities: Movie and Actor
#Entity
#Table(name = "MOVIE")
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private Long id;
#Column(name = "TITLE")
#Basic
private String title;
#Column(name = "GENRE")
#Basic
private String genre;
#Column(name = "RATING")
#Basic
private double rating;
#Column(name = "WATCH_TIME")
#Basic
private double watchTime;
#Column(name = "RELEASE_YEAR")
#Basic
private int releaseYear;
}
#Entity
#Table(name = "ACTOR")
public class Actor {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
#Basic
private String name;
#Column(name = "FAMILY")
#Basic
private String family;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "MOVIE_ID")
#Fetch(FetchMode.JOIN)
private Movie movie;
#Basic
#Column(name = "DATE_TIME")
private Timestamp dateTime;
}
Also I have their repositories which extends JpaSpecificationExecutor<>
And my ActorSpecification is as below:
public class ActorSpecification implements Specification<Actor> {
private List<SearchCriteria> list;
public ActorSpecification() {
this.list = new ArrayList<>();
}
public void add(SearchCriteria criteria) {
list.add(criteria);
}
#Override
public Predicate toPredicate(Root<Actor> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
//create a new predicate list
List<Predicate> predicates = new ArrayList<>();
//add criteria to predicates
for (SearchCriteria criteria : list) {
Join<Actor, Movie> join = root.join(Actor_.MOVIE);
query.orderBy(builder.desc(root.get(Actor_.DATE_TIME)));
if (criteria.getOperation().equals(SearchOperation.IN_MOVIE_ID)) {
predicates.add(join.get(Movie_.ID).in(Arrays.asList(72, 74, 76, 78)));
} else if (criteria.getOperation().equals(SearchOperation.IN_MOVIE_WATCHTIME)) {
predicates.add(join.get(Movie_.WATCH_TIME).in(Arrays.asList(135, 126)));
}
}
return builder.and(predicates.toArray(new Predicate[0]));
}
}
And this is the way I use to filter my data and fetch data:
ActorSpecification actorsInMovieIdList = new ActorSpecification();
actorsInMovieIdList.add(new SearchCriteria("MovieId", "72, 74, 76, 78", SearchOperation.IN_MOVIE_ID));
List<Actor> actorsMovieIdList = actorRepository.findAll(actorsInMovieIdList);
ActorSpecification actorsInMovieWatchTime = new ActorSpecification();
actorsInMovieWatchTime.add(new SearchCriteria("MovieWatchTime", "135 ,126", SearchOperation.IN_MOVIE_WATCHTIME));
List<Actor> actorsMoviesWatchTime = actorRepository.findAll(actorsInMovieIdList.and(actorsInMovieWatchTime));
AND NOW MY REQUIREMENT:
As we have many Actor in each Movie, so the join result will return list of Actors of each movie that matches our conditions for filtering movies.
Now I need to just return the Actor of that movie which has the greatest DATE_TIME
,is there any way for doing it with JpaSpecification or I need to implement a filter method myself.
If I want to tell you about my real project in order to make it more clear.
I have STOCK and DAILY_PRICE Tables and of course any Stock has many Daily_Price, So I just want to fetch the last Daily_price joining my Stock record.
Can anyone help me in this issue??
Any help will be appreciated!!

How to persist entity with joining?

I am confused about how to save entry in db with column's join. I have #Entity bellow
#XmlRootElement
#XmlAccessorType(value = XmlAccessType.FIELD)
#Entity
#Table(name = "psc_users")
#NamedQuery(name = "User.findAll", query = "SELECT u FROM User u")
public class User implements Serializable {
private static final long serialVersionUID = 8885916014620036457L;
#Id
private static final String SEQUENCE_NAME = "psc_users_user_id_seq";
#Id
#GeneratedValue(generator = "UseExistingOrGenerateIdGenerator",
strategy = GenerationType.SEQUENCE)
#GenericGenerator(name = "UseExistingOrGenerateIdGenerator",
strategy = "com.psc.util.UseExistingOrGenerateIdGenerator",
parameters = {
#org.hibernate.annotations.Parameter(name = "sequence", value = SEQUENCE_NAME)
}
)
#Column(name = "USER_ID")
private Long userId;
#Column(name = "DEF", length = 30)
private String def;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "DEL_DATE")
private Date delDate;
#Column(name = "DISPLAY_DEF", length = 60)
private String displayDef;
#Column(name = "EMAIL", length = 60)
private String email;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "NAVI_DATE")
private Date naviDate;
#Column(name = "NAVI_USER")
private String naviUser;
#Column(name = "PHONE", length = 30)
private String phone;
#Column(name = "PWD", length = 40)
private String pwd;
//bi-directional many-to-one association to Branch
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "BRNC_BRNC_ID", nullable = false)
private Branch pscBranch;
public Long getBrncBrncId() {
return brncBrncId;
}
public void setBrncBrncId(Long brncBrncId) {
this.brncBrncId = brncBrncId;
}
#Column(name = "BRNC_BRNC_ID", insertable = false, updatable = false)
private Long brncBrncId;
//bi-directional many-to-one association to User
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "CURATOR_USER_ID")
private User pscUser;
public Long getCuratorUserId() {
return curatorUserId;
}
public void setCuratorUserId(Long curatorUserId) {
this.curatorUserId = curatorUserId;
}
#Column(name = "CURATOR_USER_ID", insertable = false, updatable = false)
private Long curatorUserId;
public User() {
}
public Long getUserId() {
return this.userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getDef() {
return this.def;
}
public void setDef(String def) {
this.def = def;
}
public Date getDelDate() {
return this.delDate;
}
public void setDelDate(Date delDate) {
this.delDate = delDate;
}
public String getDisplayDef() {
return this.displayDef;
}
public void setDisplayDef(String displayDef) {
this.displayDef = displayDef;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getNaviDate() {
return this.naviDate;
}
public void setNaviDate(Date naviDate) {
this.naviDate = naviDate;
}
public String getNaviUser() {
return this.naviUser;
}
public void setNaviUser(String naviUser) {
this.naviUser = naviUser;
}
public String getPhone() {
return this.phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getPwd() {
return this.pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public Branch getPscBranch() {
return this.pscBranch;
}
public void setPscBranch(Branch pscBranch) {
this.pscBranch = pscBranch;
}
public User getPscUser() {
return this.pscUser;
}
public void setPscUser(User pscUser) {
this.pscUser = pscUser;
}
}
if I save User instance without field pscUser (here null) but there is valid CuratorUserId with correct value I end up in a situation with empty CuratorUserId in db. If you look at code then you will see these bound fields.
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "CURATOR_USER_ID")
private User pscUser;
#Column(name = "CURATOR_USER_ID", insertable = false, updatable = false)
private Long curatorUserId;
code to save user
repositoryUser.save(user);
this i see in debugger
this i see in database after saving my user.
sorry for my stupid question but I come across on a different behavior, there is code in my project which behaves in another manner. I don't want to search actual another user(curator) for saving my user, because of overhead on query
The #Column annotation on the curetorUserId field has properties
insertable=false and updatable=false, which means that its value is ignored during inserts and updates.
You can either change these properties to true (but it can break your application in some other places) or just fill in pscUser field using EntityManager.getReference, which just creates a proxy and doesn't actualy produce a query to the database.
Your mapping should look like the below:
#XmlRootElement
#XmlAccessorType(value = XmlAccessType.FIELD)
#Entity
#Table(name = "psc_users")
#NamedQuery(name = "User.findAll", query = "SELECT u FROM User u")
public class User implements Serializable {
private static final long serialVersionUID = 8885916014620036457L;
#Id
private static final String SEQUENCE_NAME = "psc_users_user_id_seq";
#Id
#GeneratedValue(generator = "UseExistingOrGenerateIdGenerator",
strategy = GenerationType.SEQUENCE)
#GenericGenerator(name = "UseExistingOrGenerateIdGenerator",
strategy = "com.psc.util.UseExistingOrGenerateIdGenerator",
parameters = {
#org.hibernate.annotations.Parameter(name = "sequence", value = SEQUENCE_NAME)
}
)
#Column(name = "USER_ID")
private Long userId;
#Column(name = "DEF", length = 30)
private String def;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "DEL_DATE")
private Date delDate;
#Column(name = "DISPLAY_DEF", length = 60)
private String displayDef;
#Column(name = "EMAIL", length = 60)
private String email;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "NAVI_DATE")
private Date naviDate;
#Column(name = "NAVI_USER")
private String naviUser;
#Column(name = "PHONE", length = 30)
private String phone;
#Column(name = "PWD", length = 40)
private String pwd;
//bi-directional many-to-one association to Branch
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "BRNC_BRNC_ID", nullable = false)
private Branch pscBranch;
//bi-directional many-to-one association to User
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "CURATOR_USER_ID")
private User pscUser;
public User() {
}
}
You need to think in terms of objects. The FK will only be set in the database if you set the pscUser reference to an instance of a User. If this is an existing User then you need to set a reference to the existing persistent entity.
Real answer is that I have two points for saving and updating my entity. Please see this Hibernate: Where do insertable = false, updatable = false belong in composite primary key constellations involving foreign keys?

JPA custom query involving foreign_key

I would like to make a JPA query to count how many records are with a specific foreign key.
Entities:
#Entity(name = "person")
#Inheritance(strategy = InheritanceType.JOINED)
public class Person {
#Id
private Long id;
private String firstName;
private String lastName;
private String phoneNumber;
#OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
#JoinColumn(name = "ADRESS_FK")
private ADDRESS address;
}
#Entity(name = "address")
public class Address {
#Id
private Long id;
private String country;
private String city;
private String street;
private String number;
}
And this is what i am trying, how would this look in JPA?
public Long getOccurences( Long id ) {
Query query = _entityManager.createQuery( "Select count(*) from Persons p where p.address = ?1" );
query.setParameter( 1, id );
return query.getSingleResult() );
}

Categories