I'm having a trouble with a class that's associated with itself. The object it's this:
Category.java
package com.borjabares.pan_ssh.model.category;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import com.borjabares.pan_ssh.util.Trimmer;
#Entity
public class Category {
private long categoryId;
private String name;
private Category parent;
public Category() {
}
public Category(String name, Category parent) {
this.name = name;
this.parent = parent;
}
#SequenceGenerator(name = "CategoryIdGenerator", sequenceName = "CategorySeq")
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "CategoryIdGenerator")
public long getCategoryId() {
return categoryId;
}
public void setCategoryId(long categoryId) {
this.categoryId = categoryId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = Trimmer.trim(name);
}
#ManyToOne(optional=false, fetch=FetchType.EAGER)
#JoinColumn(name="categoryId", insertable=false, updatable=false, nullable = true)
public Category getParent() {
return parent;
}
public void setParent(Category parent) {
this.parent = parent;
}
#Override
public String toString() {
return "Category [\ncategoryId=" + categoryId + ", \nname=" + name
+ ", \nparent=" + parent + "]";
}
}
The association it's only one level deep. Insert, and other queries are working. But when I try to select only parent categories or non parent categories Hibernate only returns 0 results for parent categories or all the results of the table.
The queries, as they are right now is like this, but I've a lot of other queries with joins, is null and other methods obtaining always the same result.
#SuppressWarnings("unchecked")
public List<Category> listParentCategories() {
return getSession().createQuery(
"SELECT c FROM Category c WHERE c.parent is null ORDER BY c.name")
.list();
}
Thank you, and sorry for the mistakes I've maybe made writing this.
EDIT:
Insertion works fine, when I list all the categories in jUnit and print them I've got this:
Category [
categoryId=416,
name=Deportes,
parent=null],
Category [
categoryId=417,
name=Formula 1,
parent=Category [
categoryId=416,
name=Deportes,
parent=null]],
Category [
categoryId=418,
name=F?tbol,
parent=Category [
categoryId=416,
name=Deportes,
parent=null]]
Besides in the insertion I'm controlling that a category can only be parent or child, and a category can't be his own father.
You're using the same column (categoryId) to uniquely identify a category, and to reference the parent category. This can't possibly work, since all categories would obviously have themselves as parent. You need another column to hold the parent category ID:
#Id
#SequenceGenerator(name = "CategoryIdGenerator", sequenceName = "CategorySeq")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "CategoryIdGenerator")
#Column(name = "categoryId") // not necessary, but makes things clearer
public long getCategoryId() {
return categoryId;
}
// optional must be true: some categories don't have a parent
#ManyToOne(optional = true, fetch = FetchType.EAGER)
// insertable at least must be true if you want to create categories with a parent
#JoinColumn(name = "parentCategoryId", nullable = true)
public Category getParent() {
return parent;
}
Related
I'm struggling with this problem. I have table "Cities" which has foreign key to table "Countries" with country_id referenced to country from which is city. In my web application I can list all the data from "Cities" table but I can't find a way to list name of country. This is my service class method.
public List<City> listAll() {
List<City> cities = repo.findAll();
return cities;
}
In "City" entity I have field Country by which I can find in method name of country but I don't know how to return it together with cities.
Addition:
#GetMapping("/cities")
public String getAllCities(Model model) {
List<City> listCities = service.listAll();
model.addAttribute("showListCities", listCities);
return "cities";
}
City.java:
package com.bookflight.BookFlight.gradovi;
import com.bookflight.BookFlight.drzave.Drzave;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "cities")
public class City {
#Id
#Column(name = "city_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false, length = 45, name = "city_name")
private String city_name;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "cou_id", referencedColumnName = "cou_id")
private Country countries;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getcity_name() {
return city_name;
}
public void setcity_name(String city_name) {
this.city_name = city_name;
}
public Countries getCountries() {
return countries;
}
public void setCountries(Country countries) {
this.countries = countries;
}
}
NOTE: Every variable name here is in my native language so I literally translated it word by word to better understand your solution afterwards.
You have add FetchType to the #ManyToOne annotation arguments:
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "cou_id", referencedColumnName = "cou_id")
private Country countries;
and her a short description for each fetch type:
FetchType.LAZY will only fire for primary table. If in your code you call any other method that has a parent table dependency then it will fire query to get that table information.
FetchType.EAGER will create join of all table including relevant parent tables directly.
And you can add a method in your city Class to return your country name and this method will be available in your view-layer:
public String getCountryName(){
return countries == null ? null : countries.getName();
//not sure how the country class is implemented
}
I created two tables -student and subject.
Then I created a third table student_subject, which is a joint table to connect students with subjects. It has 5 fileds: row id, student id, subject id and the created_at and updated_at fields (which comes from the AuditModel class):
package com.rest.web.postgresapi.data.models;
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(columnNames = {"student_id", "subject_id"})
})
public class StudentSubject extends AuditModel {
#Id
#GeneratedValue(generator = "enrollment_generator")
#SequenceGenerator(
name = "enrollment_generator",
sequenceName = "enrollment_sequence",
initialValue = 4420
)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "student_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private Student student_id; // If I put private Long student_id, it fails
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "subject_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private Subject subject_id; // If I put private Long subject_id, it fails
// Constructors
protected StudentSubject() {}
public StudentSubject(Student student_id, Subject subject_id) {
this.student_id = student_id;
this.subject_id = subject_id;
}
// Getters
public Long getId() {
return id;
}
public Student getStudent_id() {
return student_id;
}
public Subject getSubject_id() {
return subject_id;
}
// Setters
public void setId(Long id) {
this.id = id;
}
public void setStudent_id(Student student) {
this.student_id = student;
}
public void setSubject_id(Subject subject) {
this.subject_id = subject;
}
}
The application perfectly creates the tables in the database and I can get and post in the student and subject tables. No problem with that. The pain comes with the controller for the joint table.
This is the controller for the student_subject joint table table
package com.rest.web.postgresapi.controllers;
import com.rest.web.postgresapi.data.models.StudentSubject;
import com.rest.web.postgresapi.repository.StudentSubjectRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.List;
#RestController
public class StudentSubjectController {
#Autowired
private StudentSubjectRepository studentSubjectRepository;
#GetMapping("/enrollments")
public List<StudentSubject> getAllStudentsSubjects(){
return studentSubjectRepository.findAll();
}
#PostMapping("/enrollments/student/subject")
public StudentSubject createStudentSubject(#Valid #RequestBody StudentSubject studentSubject) {
return studentSubjectRepository.save(studentSubject);
}
}
There are two problems:
1 .- when I do the get from the student_subject table, It only retrieves the id of the row and the created_at and updated_at fields. No student_id nor subject_id.
response from get
2 .- when I do the post (from postman) to insert a row, I got the following error:
Detail: Failing row contains (4671, 2018-11-20 11:04:34.176, 2018-11-20 11:04:34.176, null, null).
I provide both student_id and subject_id, as you can see at this screenshot from postman, but the error clearly states both fields are null:
postman post
It seems that my definition of the table is somehow wrong. What am I missing in my code?
Spring MVC uses Jackson to serialize/deserialize Java objects to/from JSON.
If you annotate an attribute with #JSONIgnore then Jackson will ignore it.
This is the reason why you don't see the student_id field or the subject_id field in your JSON response of the GET method. Because Jackson is ignoring them when converts from object to json.
And this is the reason why your POST fails. Because Jackson is ignoring the received attributes. Jackson is creating an empty entity and JPA is saving the entity without student_id and subject_id.
Solved by replacing
#JsonIgnore
with
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
as indicated in this answer
I am using JPA createquery API to fetch the data.
Here is my query data
#PersistenceContext
EntityManager entityManager;
#Override
public List<String> fetchAllReleaseNumbers() {
Query query = entityManager.createQuery("SELECT release FROM ReleaseModel", String.class);
return query.getResultList();
}
and here is my pojo class.
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "dbname.tablenamefromDB")
public class ReleaseModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "dbcolumnname", unique = true, nullable = false)
private String release;
#Column(name = "dbcolumnname")
private String releaseDesc;
#Column(name = "dbcolumnname")
private Integer releaseStatus;
#Column(name = "dbcolumnname")
private Integer releaseMode;
public String getRelease() {
return release;
}
public void setRelease(String release) {
this.release = release;
}
public String getReleaseDesc() {
return releaseDesc;
}
public void setReleaseDesc(String releaseDesc) {
this.releaseDesc = releaseDesc;
}
public Integer getReleaseStatus() {
return releaseStatus;
}
public void setReleaseStatus(Integer releaseStatus) {
this.releaseStatus = releaseStatus;
}
public Integer getReleaseMode() {
return releaseMode;
}
public void setReleaseMode(Integer releaseMode) {
this.releaseMode = releaseMode;
}
}
Though the table exists in db its throwing not exist.Any ideas where I made mistake.
I tried whether any aliases can be given to the table name.
I am using pojo class name only for createQuery.
TIA.
You should specify a schema name by this way
#Table(schema = "dbname", name = "tablenamefromDB")
You have an incorrect mapping:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "dbcolumnname", unique = true, nullable = false)
private String release;
I think String can't be auto generated.
Also all your columns have dbcolumnname name.
The issue was that the schema was not specified in the entity class or the user did not login using proxy. If the user login using a proxy access i.e. userName[schemaName] they do not need to specify schema in the entity class. But if the user login using just the userName, they need to specify the schema in the entity. This is to specify where the table can be found in the database.
To demonstrate my problem, I created a simple Spring Boot application. It has one Entity, which has ID, two String properties and two Sets<String> sets.
package com.mk.cat.domain;
import javax.persistence.*;
import java.util.Set;
#Entity
#Table(name = "cat")
public class Cat {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "sex")
private String sex;
#ElementCollection(fetch = FetchType.EAGER)
#Column(name = "color")
#CollectionTable(
name = "cat_color",
joinColumns = #JoinColumn(name = "cat_id"))
private Set<String> colors;
#ElementCollection(fetch = FetchType.EAGER)
#Column(name = "nickname")
#CollectionTable(
name = "cat_nickname",
joinColumns = #JoinColumn(name = "cat_id"))
private Set<String> nicknames;
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 String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Set<String> getColors() {
return colors;
}
public void setColors(Set<String> colors) {
this.colors = colors;
}
public Set<String> getNicknames() {
return nicknames;
}
public void setNicknames(Set<String> nicknames) {
this.nicknames = nicknames;
}
}
There is also a simple code, which persists and loads the Cat Entity from DB.
package com.mk.cat;
import com.google.common.collect.Sets;
import com.mk.cat.domain.Cat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class CatApplication implements CommandLineRunner {
private final CatRepository catRepository;
private static final Logger LOGGER = LoggerFactory.getLogger(CatApplication.class);
#Autowired
public CatApplication(CatRepository catRepository) {
this.catRepository = catRepository;
}
public static void main(String[] args) {
SpringApplication.run(CatApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
Cat cat = new Cat();
cat.setName("Ben");
cat.setSex("Male");
cat.setNicknames(Sets.newHashSet("Fluffy", "Mr. Tomcat", "Catburger"));
cat.setColors(Sets.newHashSet("Black", "White"));
final Cat saved = catRepository.save(cat);
LOGGER.info("Cat saved={}", cat);
catRepository.findOne(saved.getId());
}
}
I traced Hibernate and I found, that the Cat is loaded from DB by this SQL.
select cat0_.id as id1_0_0_,
cat0_.name as name2_0_0_,
cat0_.sex as sex3_0_0_,
colors1_.cat_id as cat_id1_1_1_,
colors1_.color as color2_1_1_,
nicknames2_.cat_id as cat_id1_2_2_,
nicknames2_.nickname as nickname2_2_2_
from cat cat0_
left outer join cat_color colors1_ on cat0_.id=colors1_.cat_id
left outer join cat_nickname nicknames2_ on cat0_.id=nicknames2_.cat_id
where cat0_.id=1
The Hibernate then gets this Cartesian product from the rows of the cat table and two tables, that represent the Cat#colors and Cat#nicknames sets.
id1_0_0_ name2_0_0_ sex3_0_0_ cat_id1_1_1_ color2_1_1_ cat_id1_2_2_ nickname2_2_2_
1 Ben Male 1 Black 1 Fluffy
1 Ben Male 1 Black 1 Catburger
1 Ben Male 1 Black 1 Mr. Tomcat
1 Ben Male 1 White 1 Fluffy
1 Ben Male 1 White 1 Catburger
1 Ben Male 1 White 1 Mr. Tomcat
Hibernate then goes through each and every line, parses every single item of the ResultSet and creates the Entity. Is it somehow possible to optimize this approach? I would like to select the Cat#colors and Cat#nicknames sets by a subselect, due to serious performance problems. In the real case, I fetch 1500 Entities, that have complex structure and it is not uncommon, that one fetched Entity generates 25.000 rows in the corresponding ResultSet causing a very long parsing time.
The lazy loading in my case is not the option I would like to use, because it brings clutter to the code. As far as I know, the lazily loaded Collection must be initialized by first call and this is quite a big usability price to pay in my real application.
I would appreciate 3 separate selects, one from the cat table, one from the cat_color table and one from the cat_nickname table.
I found the solution for Hibernate, the #Fetch(FetchMode.SELECT) did the work, because it made Hibernate to select the nicknames by a separate select instead of join.
#Fetch(FetchMode.SELECT)
#ElementCollection(fetch = FetchType.EAGER)
#Column(name = "nickname")
#CollectionTable(
name = "cat_nickname",
joinColumns = #JoinColumn(name = "cat_id"))
private Set<String> nicknames;
Im using HibernateTemplate3 + Spring, and I find myself needing a list of ProfilingReport objects by date, grouping by their average times.
The problem is that apparently Hibernate can not map my selection to the ProfilingReport object from my model.
Id like to know if there is a way to do this, since it is just returning a list of arrays of objects at the moment.
This is the beggining of my ProfilingReport class (minus getters and setters):
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Temporal;
#Entity
#Table(name = "ProfilingReport")
public class ProfilingReport extends Persistent {
private String serviceName;
private long runTime;
#Temporal(javax.persistence.TemporalType.DATE)
private Date date;
My Persistent class from which all persistent classes extend:
import java.io.Serializable;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
#MappedSuperclass
public class Persistent implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Long version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
}
And this is the method im trying to execute in my DAO:
#Override
#Transactional(readOnly = true)
public List<ProfilingReport> getProfilingReports(final Date fecha) {
String query = "select p.id, p.version, p.serviceName, AVG(p.runTime), date "
+ "from Profiling p "
+ "where p.date = :fecha "
+ "group by p.serviceName";
return this.hibernateTemplate.findByNamedParam(query, "fecha", fecha);
}
Try this hql changes,
#Override
#Transactional(readOnly = true)
public List<ProfilingReport> getProfilingReports(final Date fecha) {
String query = "from ProfilingReport p "
+ "where p.date = :fecha "
+ "group by p.serviceName";
return this.hibernateTemplate.findByNamedParam(query, "fecha", fecha);
}
Then you need to find average of runTime by java business logic itself.
Otherwise with your existing list of array object you can iterate and create ProfilingReport object and assign these array values to relevant properties in ProfilingReport. Finally add each ProfilingReport object into a list(List).
On solution would be to use Hibernate org.hibernate.annotations.Formula annotation:
#Entity
#Table(name = "ProfilingReport")
public class ProfilingReport extends Persistent {
private String serviceName;
private long runTime;
#Temporal(javax.persistence.TemporalType.DATE)
private Date date;
#Formula(
"select AVG(p.runTime) from Profiling p group by p.serviceName"
)
private Number averageRunTime;
}
And then just run your query by date:
#Override
#Transactional(readOnly = true)
public List<ProfilingReport> getProfilingReports(final Date fecha) {
return this.hibernateTemplate.findByNamedParam(
"from ProfilingReport p where p.date = :fecha",
"fecha",
fecha
);
}
Another solution is to use SQL window functions if your database supports them, then you just need a native query and the window function allows you to retain the selected rows while embedding the aggregate result, just like you wanted in the first place.