I have simple code like:
#Transactional(readOnly = true, timeout = 10)
public List<MyClass> findThem() {
Criteria criteria = getSession().createCriteria(MyClass.class);
#SuppressWarnings("unchecked")
List<MyClass> theList = criteria.list();
return theList;
}
When I use this for the query, I get back 942 items, with duplicated items in the list. I don't see the pattern about which ones are duplicated just yet.
If I query with DB directly, I get back 138 records. 138 is the correct number of results:
SELECT count(*) FROM theschema.MY_CLASS;
I tried using the class with and without Lombok's #EqualsAndHashCode(exclude="id") on MyClass and also just not including any equals() implementation, thinking this was the cause - but no luck.
I've recently upgraded from MySQL 5.6 to MySQL 5.7.17 but I'm still on Hibernate 4.3.8.Final and HikariCP 2.5.1. The query has no WHERE clauses or anything very complicated. It may not matter, but I'm on Spring 4.3.3.RELEASE with mysql-connector-java 5.1.39.
Any other suggestions where to look? I'm going to turn on more debug logging and take a closer look in the meantime.
Update:
I'm able to correct this with criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY); but not sure why it's now necessary.
Update 2
Here's MyClass. It has more basic properties, but I excerpted them for brevity. Some of the names are renamed for privacy reasons.
import base.domain.model.Persistable;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.validator.constraints.NotEmpty;
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.*;
#NoArgsConstructor
#Entity
#Table(name = "MY_CLASS")
public class MyClass implements Persistable {
#Getter
#Setter
#Id
#Column(name = "MY_CLASS_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Getter
#Setter
private ClassB classb;
#Getter
#Setter
#ManyToOne
#JoinColumn(name = "CLASS_C_ID")
private ClassC classC;
#Getter
#Setter
#Transient
private Integer daysOpen;
#Getter
#Setter
#OneToOne
#JoinColumn(name = "CLASS_D_ID")
private ClassD classD;
#Getter
#Setter
#NotEmpty
private String briefDescription;
#Getter
#Setter
#Column(length = 1000)
private String details;
#Getter
#Setter
#OneToOne
#JoinColumn(name = "COMPANY_ID")
private Company company;
#Getter
#Setter
#Transient
private BigDecimal cost;
#Getter
#Setter
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "MY_CLASS_TAG",
foreignKey = #ForeignKey(name = "FK_MY_CLASS_TAG_REQUEST"),
joinColumns = #JoinColumn(name = "MY_CLASS_ID"),
inverseJoinColumns = #JoinColumn(name = "TAG_ID"))
private Set<Tag> tags = new HashSet<>(10);
#Getter
#Setter
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "MY_CLASS_ASSIGNEE",
foreignKey = #ForeignKey(name = "FK_MY_CLASS_ASSIGNEE"),
joinColumns = #JoinColumn(name = "MY_CLASS_ID"),
inverseJoinColumns = #JoinColumn(name = "ASSIGNEE_ID"))
private Set<Account> assignees = new HashSet<>(0);
#Getter
#Setter
#OneToMany(cascade = CascadeType.ALL, mappedBy = "myClass")
private List<MyClassHistory> historyList = new ArrayList<>(1);
private static final long serialVersionUID = 1L;
}
Update 3:
I am also able to get the correct results using HQL (abridged) without the result transformer:
String hql = "from MyClass myClass";
Query q = getSession().createQuery(hql);
List<MyClass> myClasses = q.list();
return myClasses;
Going to compare the SQL output for each (there's a lot of output to try to understand).
there must be some join happening in the query, which increases resultset and hence your count,often I face similar issue, you can try to take data in Set instead of list, or observe the actual query fired in DB.
Related
I have got two entity. Employee and Project
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import lombok.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
#Entity
#Table(name = "EMPLOYEE_DB")
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int emp_id;
private String name;
private String email;
private String phone;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "EMP_PRO_DB", joinColumns = {
#JoinColumn(name = "empId", referencedColumnName = "emp_id")
},
inverseJoinColumns = {
#JoinColumn(name = "proId", referencedColumnName = "pro_id")
}
)
// #JsonManagedReference
private Set<Project> projects;
}
and
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;
import lombok.*;
import java.util.Set;
#Entity
#Table(name = "PROJECT_DB")
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int pro_id;
private String projectName;
private String projectType;
#ManyToMany(mappedBy = "projects", fetch = FetchType.LAZY)
#JsonBackReference
private Set<Employee> employees;
}
So there were three tables created:
employee_db
project_db
emp_pro_db
I have a EmployeeRepo and ProjectRepo. I can add data to both employee_db and project_db.
Now I would like to assignProject.
I have tried using workbench to insert: INSERT INTO emp_pro_db (emp_id, pro_id) VALUES (1, 6);
Which is working.
How do I use Jpa to do that?
I'm trying to do a simple #OneToMany relationship between contract and asset. But when hibernate tries to save , it's comming as null. What am I doing wrong?
#Entity
#Data
#EqualsAndHashCode
#NoArgsConstructor
#Table(name = "contracts")
public class Contract {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "contractId")
private List<Asset> assets;
}
#Entity
#Data
#Table(name = "assets")
public class Asset {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#ManyToOne
#JoinColumn(name = "contractId", referencedColumnName = "id")
private Contract contractId;
}
#Repository
public interface ContractRepository extends CrudRepository<Contract, Integer> {
}
private void mapAndSave(ContractDTO contractDTO) {
Contract contractToSave = new Contract();
ModelMapper mapper = BiModelMapper.createModelMapperDtoToEntity();
mapper.map(contractDTO, contractToSave);
contractRepository.save(contractToSave);
}
Caused by: java.sql.SQLIntegrityConstraintViolationException: Column 'contractId' cannot be null
The solution I was able to do is change my column table Asset.contractId to NOT NULL. Because Hibernate tries to insert the row, and after that updates the contractId.
And I change to unidirectional relationship, using only #OneToMany on Contract side.
I'm trying insert the id of Person in child table 'Contact'. But the Hibernate stores the value null at fk column.
I perform the mapping of a DTO to the entity, where it already brings the person's data and contact. In the end I have to save the person entity.
There is a table inheritance!
Parent table:
#Entity
#Table
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING, columnDefinition = "CHAR(2)", length = 2)
public abstract class Person implements Serializable {
#OneToMany(fetch = FetchType.EAGER, mappedBy = "person")
#Cascade(value={org.hibernate.annotations.CascadeType.ALL})
private #Getter #Setter Set<Contact> contacts;
}
Table Company extends of Person:
#Entity
#Table
#DiscriminatorValue(value="PJ")
public class Company extends Person implements Serializable {
#Column(nullable = false, columnDefinition = "DATE")
private #Getter #Setter LocalDate constitutionDate;
}
And where is the problem!
#Entity
#Table(name = "contact")
#EqualsAndHashCode
public class Contact implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private #Getter Integer id;
#Column(columnDefinition = "VARCHAR(16)", length = 16, nullable = false)
private #Getter #Setter String phoneNumber;
#ManyToOne(fetch = FetchType.LAZY, optional = false,targetEntity=Person.class)
#JoinColumn(name = "person_id", nullable = false, referencedColumnName="id")
private #Getter #Setter Person person;
public Contact() {}
public Contact(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
What am I doing wrong? What's the best strategy?
Excuse me for my poor English!
Can't find nothing obvious that's wrong but let me try some things:
Your cascade annotation on Person class doesn't need the specific Hibernate enumeration You can use like :
#OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL }, mappedBy = "cliente")
In your ManyToOne annotation on Contact class please try to add:
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
BTW, an inheritance relationship between Person and Company doesn't seem logic to me, but that has nothing to do with your stated problem for sure.
at the moment I develop a small eCommerce App with Spring Boot and AngularJS. For my data-access layer I use Spring Data JPA with a MySQL-DB. My next step is, that I want to plot some statistics for specific products. For example: how did the review-ratings from a specific product develop over time. So I would like to specify a period of time (e.g. 01.2016 - 03.2016) and than formulate one query that returns 10 or 15 Points in time (within the range) with the average rating of the reviews in this period. So that i can plot a Chart from that.
I found ways with Spring Data to get the average for one period (Between x and y), but then I would have to make 10 queries to the database (for each dot). So I want to know, if it is possible to formulate a query with spring data that splits a time-period in a fixed number of sub-periods and gets the average of customer-ratings within each sub-period? If yes, how can I achieve that?
An excerpt of my Data Model Looks as follows:
#Entity
#Table(name = "product_placement")
public class ProductPlacement implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "product_placement_id")
private long id;
#Column(name = "title")
private String title;
...
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy="productPlacements")
private Set<CustomerReview> customerReviews;
}
#Entity
#Table(name = "customer_review")
public class CustomerReview implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "customer_review_id")
private String reviewIdentifier;
...
#Column(name = "rating")
private Integer rating;
#Column(name = "dateOfCreation", nullable=true, unique=false)
private LocalDateTime dateOfCreation;
#JsonBackReference
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "review_to_product",
joinColumns = #JoinColumn(name = "customer_review_id"),
inverseJoinColumns = #JoinColumn(name = "product_placement_id")
)
private Set<ProductPlacement> productPlacements;
}
Thank you!
I have a many-to-many relation in my project and although I'm able to write in my two Entities table, the relational table does not get anything written.
Here's how I'm declaring this using JPA annotations:
Professor.java
#Entity
#Table(name = "Professor")
public class Professor implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "idProfessor", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "ALUNO_PROFESSOR",
joinColumns = #JoinColumn(name = "idProfessor", referencedColumnName = "idProfessor"),
inverseJoinColumns = #JoinColumn(name = "idAluno", referencedColumnName = "idAluno"))
private List<Aluno> alunoList;
// getters and setters
}
Aluno.java
#Entity
#Table(name = "Aluno")
public class Aluno implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "idAluno", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany(mappedBy = "alunoList", fetch = FetchType.EAGER)
private List<Professor> professorList;
// getters and setters
}
And here is the service layer to insert into database:
#Autowired
private AlunoDao alunoDao;
#Autowired
private ProfessorDao professorDao;
#RequestMapping(value = RestUriConstants.SUBMETER, method = RequestMethod.POST)
public #ResponseBody JsonResponse submeter(#RequestBody final Aluno aluno) {
Professor professor = professorDao.find(1);
aluno.setProfessorList(Arrays.asList(professor));
alunoDao.persist(aluno);
...
}
In this case, please consider that I already have an entry with id "1" for Professor.
As I said, it does write on Aluno and Professor table but does NOT write anything into ALUNO_PROFESSOR table.
I've already taken a look at these three kind of similiar questions but none of them could help me:
Hibernate and Spring: value of many-to-many not inserted into generated table
JPA many-to-many persist to join table
How to persist #ManyToMany relation - duplicate entry or detached entity
EDIT - Adding more code snippets
JpaAlunoDao.java
#Repository
public class JpaAlunoDao implements AlunoDao {
#PersistenceContext
private EntityManager em;
#Transactional
public void persist(Aluno aluno) {
em.persist(aluno);
}
}
JpaExercicioDao.java
#Repository
public class JpaExercicioDao implements ExercicioDao {
#PersistenceContext
private EntityManager em;
#Transactional
public void persist(Exercicio exercicio) {
em.persist(exercicio);
}
}
Try this:
public class Professor {
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "ALUNO_PROFESSOR",
joinColumns = #JoinColumn(name = "idProfessor", referencedColumnName = "idProfessor"),
inverseJoinColumns = #JoinColumn(name = "idAluno", referencedColumnName = "idAluno"))
private List<Aluno> alunoList;
}
public class Aluno {
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "ALUNO_PROFESSOR",
joinColumns = #JoinColumn(name = "idAluno", referencedColumnName = "idAluno"),
inverseJoinColumns = #JoinColumn(name = "idProfessor", referencedColumnName = "idProfessor"))
private List<Professor> professorList;
}
This will ensure that the metadata for the many-to-many relationship is available on both the entities and that operations on either side of the relationship are cascaded to the other side.
I also suggest replacing FetchType.EAGER with FetchType.LAZY for better performance because this has the potential of loading a very large dataset.
I have the same issue. I swapped where the full mapping declare to the class that we will use save() function on.
In your case:
public class Aluno {
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "ALUNO_PROFESSOR",
joinColumns = #JoinColumn(name = "idAluno"),
inverseJoinColumns = #JoinColumn(name = "idProfessor")
private List<Professor> professorList;
}
public class Professor {
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "professorList",)
private List<Aluno> alunoList;
}
and It worked fine.
...
Normally, Hibernate holds the persistable state in memory. The process of synchronizing this state to the underlying DB is called flushing.
When we use the save() method, the data associated with the save operation will not be flushed to the DB unless and until an explicit call to flush() or commit() method is made.
If we use JPA implementations like Hibernate, then that specific implementation will be managing the flush and commit operations.
One thing we have to keep in mind here is that, if we decide to flush the data by ourselves without committing it, then the changes won't be visible to the outside transaction unless a commit call is made in this transaction or the isolation level of the outside transaction is READ_UNCOMMITTED.
...
From Difference Between save() and saveAndFlush() in Spring Data JPA by baeldung:
https://www.baeldung.com/spring-data-jpa-save-saveandflush
employeeRepository.saveAndFlush(new Employee(2L, "Alice"));
or
employeeRepository.save(new Employee(2L, "Alice"));
employeeRepository.flush();
Its not necessary to set many-to-many relationship on both entities.
Just remove session.setFlushMode(FlushMode.MANUAL);
By default HibernateTemplate inside of Spring set FlushMode.MANUAL
this is source code from HibernateTemplate.
if (session == null) {
session = this.sessionFactory.openSession();
session.setFlushMode(FlushMode.MANUAL);
isNew = true;
}
Some times the problem is in the way that you insert the values. I explain with an example.
User user = userFacade.find(1);
Post post = new Post("PRUEBA");
user.addPostCollection(post);
post.addUserCollection(user);
postFacade.create(post);
You have to add the post in postCollection and the user in userCollection. You have two add the correspond entity in the collections of the two entities.
Class USER
public void addPostCollection(Post post) {
if(postCollection == null){
postCollection = new ArrayList<Post>();
}
postCollection.add(post);
}
#ManyToMany(mappedBy = "userCollection")
private Collection<Post> postCollection;
Class Post
public void addUserCollection(User user){
if(userCollection == null){
userCollection = new ArrayList<User>();
}
userCollection.add(user);
}
#JoinTable(name = "USER_POST_R", joinColumns = {
#JoinColumn(name = "POSTID", referencedColumnName = "ID")}, inverseJoinColumns = {
#JoinColumn(name = "USERID", referencedColumnName = "ID")})
#ManyToMany
private Collection<User> userCollection;
Also, it is important to instance the list, for example userCollection = new ArrayList(). If you do not, the value won´t insert.