I'm new in spring boot and now I'm trying to joining two tables where one of them are composite table (idk if it's have any effect or not). Please take a look on my code and help me.
Employees.java (Model)
#Entity
#Table(name = "employees")
public class Employees {
#Id
#Column(name = "emp_no")
private long emp_no;
#Column(name = "name")
private String name;
}
Salaries.java (Model, The composite table)
#Entity
#Table(name = "salaries")
public class Salaries {
#Column(name = "emp_no") //Primary Key
private long emp_no;
#Column(name = "salary")
private int salary;
#Column(name = "date") //Primary Key
private LocalDate date;
}
EmployeeRepo.java (Repository)
public interface EmployeesRepository extends JpaRepository<Employees, Long> {
String salary = "SELECT employees.name, salaries.salary FROM employees INNER JOIN salaries ON employees.emp_no=salaries.emp_no LIMIT 0,10";
#Query(value=salary, nativeQuery = true)
List<Employees> getUserSalary();
}
EmpController.java (Controller)
#GetMapping("/salary")
public ResponseEntity<List<Employees>> getSalaries(#RequestParam(required = false) String args){
List<Employees> employees = new ArrayList<Employees>();
employeesRepository.getUserSalary().forEach(employees::add);
if(employees.isEmpty()){
return new ResponseEntity<>(employees, HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(employees, HttpStatus.OK);
}
application.properties
spring.datasource.url= jdbc:mysql://127.0.0.1:3306/employees?useSSL=false
spring.datasource.username= ""
spring.datasource.password= ""
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto= update
And the error message:
Column 'emp_no' not found.
I dont understand why I get this error when I have emp_no column both on my tables in db.
I guess your problem is
String salary = "SELECT employees.name, salaries.salary FROM employees INNER JOIN salaries ON employees.emp_no=salaries.emp_no LIMIT 0,10";
#Query(value=salary, nativeQuery = true)
List<Employees> getUserSalary();
you are trying to save name,salary in Employees which only has emp_no, name. So the error is sating you have mapped name but emp_no is not available from query.
Create POJO/model like below
public class EmpSalary {
private String name;
private double salary;
}
Then update your repository to return EmpSalary
String salary = "SELECT employees.name, salaries.salary FROM employees INNER JOIN salaries ON employees.emp_no=salaries.emp_no LIMIT 0,10";
#Query(value=salary, nativeQuery = true)
List<EmpSalary> getUserSalary();
Related
I would like to be able to include an #Entity from another table using a foreign key. I'm following guides but I'm still confused and can't seem to get this working. The end goal would be something like this:
#Entity
#Table(name = "Labor", schema = "dbo", catalog = "database")
public class LaborEntity {
private int laborId;
private Timestamp laborDate;
private Integer jobNumber;
private Integer customerId;
//mapping of customer to labor
private CustomerEntity customer;
#Id
#Column(name = "LaborID", nullable = false)
public int getLaborId() {
return laborId;
}
public void setLaborId(int laborId) {
this.laborId = laborId;
}
#Basic
#Column(name = "LaborDate", nullable = true)
public Timestamp getLaborDate() {
return laborDate;
}
public void setLaborDate(Timestamp laborDate) {
this.laborDate = laborDate;
}
#Basic
#Column(name = "JobNumber", nullable = true)
public Integer getJobNumber() {
return jobNumber;
}
public void setJobNumber(Integer jobNumber) {
this.jobNumber = jobNumber;
}
#Basic
#Column(name = "CustomerID", nullable = true)
public Integer getCustomerId() {
return customerId;
}
public void setCustomerId(Integer customerId) {
this.customerId = customerId;
}
#ManyToOne(targetEntity = CustomerEntity.class)
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(name = "CustomerID", //in this table
referencedColumnName = "CustomerID", //from CustomerEntity
insertable = false, updatable = false,
foreignKey = #javax.persistence.ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
public CustomerEntity getCustomer() {
return this.customer;
}
public void setCustomer(CustomerEntity customer) {
this.customer = customer;
}
Anyway, the end goal is to get the Customer data from the Customer table as part of the Labor entity so it can be accessed directly with something like getCustomerEntity(). I supposed I would have to accomplish it by querying first using a JOIN like so:
TypedQuery<LaborEntity> query = entityManager.createQuery(
"SELECT l FROM LaborEntity l " +
"INNER JOIN CustomerEntity c " +
"ON l.customerId = c.customerId " +
"WHERE l.laborDate = '" + date + "'", LaborEntity.class);
List<LaborEntity> resultList = query.getResultList();
And then I can simply access the Customer that's associated like so:
resultList.get(0).getCustomer().getCustomerName();
Am I dreaming or is this actually possible?
Yes, this is completely possible.
(I'm not sure about what was the question though - but assuming you only want get it working)
You query needs to be a JPQL, not an SQL. And the Join is different on JPQL:
"SELECT l FROM LaborEntity l " +
"JOIN l.customer c " +
"WHERE ... "
The join starts from the root entity and then you use the field name (not column).
You can also use JOIN FETCH, then the associated entity (customer) will be loaded in the same query. (that is, Fetch EAGER)
Other recommendations:
Don't concat the parameters like that date. Instead use setParameter.
You don't need those #Basic
You don't need that targetEntity = CustomerEntity.class. It'll be detected automatically.
I have a JPA repository for a DB entity as follows:
#Repository
public interface StudentRepository
extends JpaRepository<Student, String> {
}
Student entity is as follows:
#Entity
#Table(name = "student")
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "custom-uuid")
#GenericGenerator(name = "custom-uuid",
strategy = "generator.CustomUUIDGenerator")
private String id;
#Column(name = "roll_number")
private String rollNumber;
#Column(name = "name")
private String name;
#LastModifiedDate
#Column(name = "last_modified_date")
#JsonIgnore
private Instant lastModifiedDate = Instant.now();
Now, I would like to write the following SQL query using JPA as follows:
SELECT id, roll_number from student where id=<id> order by last_modified_date desc;
Being new to JPA/Hibernate, I don't have an idea how to achieve this using JPA/Hibernate.
Could anyone please help here ?
You could write something like:
String hql = "SELECT s.id, s.rollNumber FROM Student s WHERE s.id = :id ORDER BY s.lastModifiedDate DESC";
query.setParameter("id", 10);
Query query = session.createQuery(hql);
List results = query.list();
Refer: https://www.tutorialspoint.com/jpa/jpa_jpql.htm
Create a new method in Repository
#Repository
public interface StudentRepository extends JpaRepository<Student, String> {
/**
* List all student by criterias.
*
* #return
*/
#Query(value = "SELECT id, roll_number from student where id=?1 order by last_modified_date desc;", nativeQuery = true)
List<Student> listStudent(String id);
}
Reference document: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query
I'm trying to convert the following SQL query into JPQL query:
SELECT *
FROM movie m INNER JOIN movie_genre mg ON m.id = mg.movie_id
WHERE mg.genre_id = (SELECT mg2.genre_id FROM movie_genre mg2 WHERE mg2.movie_id = ?1 AND mg2.movie_id <> mg.movie_id AND mg.genre_id = mg2.genre_id)
GROUP BY mg.movie_id ORDER BY count(*) DESC
The problem is that i don't have a model class to represent movie_genre table because it's auto generated table from ManyToMany relationship.
so is there any way to convert that query into JPQL
In the moment I'm using a native query instead:
#Query(value = "SELECT * FROM movie m INNER JOIN movie_genre mg ON m.id = mg.movie_id " +
"WHERE mg.genre_id = (SELECT mg2.genre_id FROM movie_genre mg2 WHERE mg2.movie_id = ?1 AND mg2.movie_id <> mg.movie_id AND mg.genre_id = mg2.genre_id) " +
"GROUP BY mg.movie_id ORDER BY count(*) DESC", nativeQuery = true)
Page<Movie> findRelatedMoviesToAMovieById(#Param("id") int id, Pageable pageable);
Edit :
here's the models:
Movie
#Entity
public class Movie extends DateAudit {
private Long id;
private String name;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "movie_genre",
joinColumns = #JoinColumn(name = "movie_id"),
inverseJoinColumns = #JoinColumn(name = "genre_id")
)
private List<Genre> genres = new ArrayList<>();
}
Genre
#Entity
public class Genre {
private Long id;
private String name;
#ManyToMany(mappedBy = "genres", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<Movie> movies = new HashSet<>();
}
Despite the SQL query you provided, I re-thought your requirement and translated that to a JPQL. So far what I understood you were finding related movies to a movie having common genres. If this is correct, you can achieve this by
SELECT m FROM Movie m JOIN m.genres mg
WHERE mg.id IN
(SELECT g.id FROM Genre g JOIN g.movies gm WHERE gm.id = :movieId)
AND m.id <> :movieId
GROUP BY m ORDER BY count(*) DESC
This will generate the SQL as below
select distinct movie0_.id as id1_1_, movie0_.name as name2_1_
from movie movie0_ inner join movie_genre genres1_ on movie0_.id=genres1_.movie_id inner join genre genre2_ on genres1_.genre_id=genre2_.id
where (genre2_.id in
(select genre3_.id from genre genre3_ inner join movie_genre movies4_ on genre3_.id=movies4_.genre_id inner join movie movie5_
on movies4_.movie_id=movie5_.id where movie5_.id=?))
and movie0_.id <> ?
group by movie0_.id order by count(*) desc
Querying Collection Association in JPQL
When you are using JPQL, you are in the Object Relationship world. So when you are querying for an association that is a collection, you access them through that collection field. And when it is a ManyToMany association, so you are not having a join table that does have a mapped entity, you need to Join with the collection field. And JPA vendor automatically translates that to join with the join table.
Like if you are querying movies of certain genres, this will go
SELECT m FROM Movie m JOIN m.genres mg WHERE mg.id = :genreId
Performance Concern
As you notice in the generated SQL, there are many levels of join to fetch and filter the data. This leads to a performance bottleneck. To overcome this, you can have entity for the movie_genre table.
This scenario is fairly discussed here
The best way to map a many-to-many association with extra columns when using JPA and Hibernate
You do not need a model movie_genre in order to write a JPQL statement. Hibernate knows about it implicitly when doing a JPQL statement.
Example:
SELECT m from Movie m left join m.genres g where g.name = 'Action'
The bidirectional "m.genres g" works with all bidirectional JPQL statements, including many-to-many where the association model is implicit and not actually present.
Example Code:
#Entity
#Table(name = "MOVIE")
public class Movie {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "movie_genre",
joinColumns = #JoinColumn(name = "movie_id"),
inverseJoinColumns = {#JoinColumn(name = "genre_id")}
)
private Set<Genre> genres = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Genre> getGenres() {
return genres;
}
public void setGenres(Set<Genre> genres) {
this.genres = genres;
}
}
#Entity
#Table(name = "GENRE")
public class Genre {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#ManyToMany(mappedBy = "genres", cascade = {CascadeType.ALL})
private Set<Movie> movies = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Movie> getMovies() {
return movies;
}
public void setMovies(Set<Movie> movies) {
this.movies = movies;
}
}
Working JPQL Example
#PersistenceContext
EntityManager entityManager;
#Test
#Transactional
public void test() {
Set<Movie> actionMovies = new HashSet<>();
Set<Movie> dramaMovies = new HashSet<>();
Set<Genre> dramaGenres = new HashSet<>();
Set<Genre> actionGenres = new HashSet<>();
Set<Genre> generes = new HashSet<>();
Movie actionMovie = new Movie();
actionMovie.setName("Batman");
actionMovies.add(actionMovie);
Movie dramaMovie = new Movie();
dramaMovie.setName("Forest Gump");
dramaMovies.add(dramaMovie);
Genre actionGenre = new Genre();
actionGenre.setName("Action");
generes.add(actionGenre);
actionGenres.add(actionGenre);
Genre dramaGenre = new Genre();
dramaGenre.setName("Drama");
generes.add(dramaGenre);
dramaGenres.add(dramaGenre);
//Bidirectional sets
actionGenre.setMovies(actionMovies);
dramaGenre.setMovies(dramaMovies);
actionMovie.setGenres(actionGenres);
dramaMovie.setGenres(dramaGenres);
genreRepository.saveAll(generes);
//Example JPQL join through not present association model.
Query query = entityManager.createQuery("SELECT m from Movie m left join m.genres g where g.name = 'Action'");
List<Movie> resultList = query.getResultList();
assertEquals("Batman",resultList.get(0).getName());
}
I have a (simplified) table structure that looks something like that:
customer table:
id name
-------------------
1 customer1
alias table:
customer_id alias
-------------------------------
1 customer one
1 customer uno
When I run the following query I easily get the list of aliases per customer:
select * from customer_alias where customer_id=1;
I would like to use this query in my hibernate to populate a list of type String. I tried using #Formula as follows:
#Entity
#Table(name = "customer")
public class Customer {
#Id
#Column(name = "id")
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
#Column(name="name")
private String name;
#Formula("(select alias from customer_alias where customer_id = id)")
private List<String> aliases;
// Getters, setters, etc...
}
It didn't work and I got this exception:
Could not determine type for: java.util.List, at table: customer, for columns: [org.hibernate.mapping.Formula( (select alias from customer_alias where customer_id = id) )]
Is there anyway to achieve this? Doesn't have to be with #Formula of course. Any reasonable way would be great.
Here is an SQLFiddle of my example
You could use #ElementCollection for having a List of related aliases without the need to map the whole entity:
#ElementCollection
#CollectionTable(name = "customer_alias", joinColumns = #JoinColumn(name = "customer_id") )
#Column(name = "alias")
private List<String> aliases;
See also:
Difference between #OneToMany and #ElementCollection?
I think you don't want to use OneToMany annotation as the second table is just a list of strings you want to find something more elegant that would not require me to create another entity.
You can use #ElementCollection as below:
#Entity
#Table(name="college")
class College implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="college_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int collegeId;
#Column(name="name")
private String collegeName;
#ElementCollection
#CollectionTable(name="student", joinColumns=#JoinColumn(name="college_id"))
#Column(name="student_name")
private Set<String> students;
public College() {
}
public Set<String> getStudents() {
return students;
}
public void setStudents(Set<String> students) {
this.students = students;
}
public int getCollegeId() {
return collegeId;
}
public void setCollegeId(int collegeId) {
this.collegeId = collegeId;
}
public String getCollegeName() {
return collegeName;
}
public void setCollegeName(String collegeName) {
this.collegeName = collegeName;
}
#Override
public String toString() {
return "College [collegeId=" + collegeId + ", collegeName=" + collegeName + ", students=" + students + "]";
}
}
I don't think #Formula annotation supports collection it can only be applied for single-valued properties. Can't say if if there exists any tweak.
I'm new to hibernate. My problem is that I have an Oracle database. I have a view in the database. Now I want to use hibernate to retrieve data in that view. Is there any possible solutions?
Below Snippet can solve your problem, which has been extracted from the tutorial: Mapping Hibernate Entities to Views
Database Query
CREATE OR REPLACE VIEW cameron AS
SELECT last_name AS surname
FROM author
WHERE first_name = 'Cameron';
view entity
#Entity
#NamedNativeQuery(name = "findUniqueCameronsInOrder", query = "select * from cameron order by surname", resultClass = Cameron.class)
public class Cameron implements java.io.Serializable {
private static final long serialVersionUID = 8765016103450361311L;
private String surname;
#Id
#Column(name = "SURNAME", nullable = false, length = 50)
public String getSurname() {
return surname;
}
public void setSurname(final String surname) {
this.surname = surname;
}
}
Hibernate mapping file.
<mapping class="examples.hibernate.spring.query.domain.Cameron" />
finally some test !...
#Test
public void findTheCameronsInTheView() throws Exception {
final List<Cameron> camerons = findUniqueCameronsInOrder();
assertEquals(2, camerons.size());
final Cameron judd = camerons.get(0);
final Cameron mcKenzie = camerons.get(1);
assertEquals("Judd", judd.getSurname());
assertEquals("McKenzie", mcKenzie.getSurname());
}
A view is from accessing data nothing different from table, a problem arises when you want to add,update or delete from view.
Please read http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/querysql.html
It' very similar to mapping ordinary database table.
Create an Entity and use your view name as Table name.
#Entity
#Table(name = "rc_latest_offer_details_view")
public class OfferLatestDetailsViewEntity {
#Id
#Column(name = "FK_OFFER_ID")
private int offerId;
#Column(name = "MAX_CHANGED_DTM")
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
private DateTime changedDateTime;
private BigDecimal price;
...
}
Then query for entities same way as you do for normal table.
Working in Hibernate 4, Spring 4.
we can achieve this by using # Immutable annotation in entity class to map database view with Hibernate
For example : I have created one database view user_data which have 2 columns (id and name) and mapped user_data view in the same way as database tables.
#Entity
#Table(name = "user_data")
#Immutable
public class UserView {
#Id
#Column(name = "ID")
private int ID ;
#Column(name = "NAME")
private String name ;
}