I have an issue with Hibernate in spring-boot.
So, mapping in spring-boot is like this:
Class A
#ManyToOne(fetch = FetchType.LAZY)
private C c;
#OneToMany(cascade = CascadeType.PERSIST,
mappedBy = "a", fetch = FetchType.LAZY)
private List<B> b;
Class B
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "a", nullable = false)
#JsonIgnore
private A a;
Now when I do update (repositoryA.save(a)) of object of class A via JpaRepository<A, Integer> with value of b=null, hibernate generates this queries:
update a
select b from B where aId = ?
So why is this second select query needed? I tried to remove CascadeType.PERSIST but the result is same.
I don't what this, because the list can have many objects and it can affect the performance.
UPDATE:
Controller:
#PutMapping("/edit")
public A editA(#Valid #RequestBody A a, #RequestParam String idC){
return serviceA.editA(a, idC);
}
Service:
#Transactional
public A editA(A a, String idC){
C c = serviceC.getById(idC);
a.setC(c);
return repositoryA.save(a);
}
You should notice that save method either updates or creates a record depending on whether it already exists or not. If you don't want to the select afterwards, maybe try just a plain "update"?
Related
Very simple problem, but it looks like is impossible to achieve what I want
The entities:
public class C {
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "column")
private Set<B> cards = new HashSet<>();
}
public class B {
#ManyToOne(fetch = FetchType.EAGER, optional = false, cascade = CascadeType.DETACH)
#JsonIgnore
#JoinColumn(name="column_id", nullable = false)
private C column;
}
#Repository
public interface BRepository extends JpaRepository<B, Long> {
}
I want to delete the B entity without use the C Repository.
if I do something like:
final C column = columnService.create(board, new C(board, "column name", 1)); //works
final B card = cardService.create(column, new B(column, "card name", 2)); //works
bRepository.delete(card); //NOTHING HAPPENS
Absolutely nothing happens on delete query, no log, data isn't removed from DB, nothing.... doesn't matter if I'm within or out a #transaction.
WHAT I'VE TRIED:
1 - if i change Set cards to FetchType.LAZY, the delete works, [but i really wanted it to be eager]
2 - if create a custom query on repository like:
#Modifying
#Query("DELETE FROM Card c where c.id = :id")
public void deleteById(#Param("id") long id);
then the delete works BUT, I've EntityListeners for this entity, and as per JPA documentation it wont work on custom query... so i need this component working
Is there a way to delete the ONE side of relationship with EAGER fetch without custom query and without loading the other side of relationship?
I don't know how to create a JPA query to get all records from my table:
#Entity
#Table(name = "A")
public class A{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, updatable = false)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "message_id")
private Message;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
}
#Entity
#Table(name="message")
#Getter
#Setter
public class Message{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, updatable = false)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "account_id", foreignKey = #ForeignKey(name = "FK_account_to_message"))
private Account account; //and then search by Account.id
}
SO I have 2 types of objects in A table (sometimes object is created from email, sometimes from a file):
one type without user_id (null) -> then I have to find all A object by searchning by Message -> Account -> Id
second type with user_id -> we can directly get A objects by values in user_id column
I want to get all records for specific user_id -> how to do that in most efficient way? I don't want to invoke 2 methods in repository:
User user = userService.getEmail();
List<A> aObjects= Stream.concat(ARepository.findByMessage_Account_Id(user.getId()).orElse(new ArrayList<>()).stream(),
aRepository.findByUser_Id(user.getId()).orElse(new ArrayList<>()).stream()).collect(Collectors.toList());
Is it possible to create ONE repository method that finds all records for 2 different objects (one with user_id and second without user_id)?
I guess that this query is to complex for using derived from the method name query. As it stated in the documentation:
Although getting a query derived from the method name is quite convenient, one might face the situation in which either the method name parser does not support the keyword one wants to use or the method name would get unnecessarily ugly. So you can either use JPA named queries through a naming convention or rather annotate your query method with #Query.
So, I would suggest just write the following query:
#Query("select aa from A aa left join aa.user u left join aa.message msg left join msg.account acc where (u is null and acc is not null and acc.id = :userId) or (u is not null and u.id = :userId)")
List<A> findByUserOrAccountId(#Param("userId") Long userId);
I have two entities. One of them is a child of the other one with a relation with OneToMany. Is it possible to implement search criteria that looks up simultaneously in both the main entity and all the child entities?
Example: I have a Company with many employees. If I search with some text, I want to retrieve all the companies, which title contains that text or its employee's names contain that text.
Here are the example entities:
#Entity
public class Company extends AbstractEntity {
#Column(nullable = false, unique = true)
private String uuid;
#Column(nullable = false)
private String companyName;
#OneToMany(mappedBy = “company”, cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
protected Set<Employee> employees = new HashSet<>();
}
#Entity
public class Employee extends AbstractEntity {
#Column(nullable = false, unique = true)
private String uuid;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = “company_id”, nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private Company company;
}
Here is the example query, that I want to transform into Specification criteria
#Query(value = “SELECT DISTINCT c from Company c left outer join c.employees e
WHERE c.companyName LIKE CONCAT('%',:text,'%')
or e.firstName LIKE CONCAT('%',:text,'%')
or e.lastName LIKE CONCAT('%',:text,'%')”)
If you are using Spring JPA data repository, your interface method would look like this.
Company findByCompanyNameConatainingOrEmployeesFirstNameConatainingOrEmployeeslastNameConataining(String searchTextCompanyTitle, String searchTextEmployeeFName, String searchTextEmployeeLName);
If you are not using data repository, please explain your data access design to get an accurate answer.
I have two entities. (Find code below)
I am trying to write a query that would count customDetails=:myCriteria of EntitiesA that are associated to EntityB of specific id.
I have written the necessary query using session.CreateSQLQuery that reads the associated_entitites table, however, I am unable to use it as the customDetails column is encrypted by hibernate's #ColumnTransformer and returns a BLOB. And I cannot replicate it in HQL as associated_entities is not mapped.
a
#Entity
public class entityA{
#Id
private int id;
#Column
#ColumnTransformer
private CustomDetails customDetails;
#ManyToMany(fetch = FetchType.EAGER,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "entitiesA")
private List<entityB> entitiesB;
//getters and setters
}
b
#Entity
public class entityB{
#Id
private int id;
#JoinTable(name = "associated_entities",
joinColumns = { #JoinColumn(name = "entityA_id") },
inverseJoinColumns = { #JoinColumn(name = "entityB_id") })
private List<EntityA> entitiesA;
//getters and setters
}
The solution I have found, but is not ideal as the logic is not done by hibernate. Had to write the logic in the DAOImpl.
Example code:
public Long getQuery(String criteria, String, fromdate, String todate){
Query theQuery = currentSession.createQuery(
"from EntityA a "+
"where a.CustomDetails >= :from "+
"and a.CustomDetails <= :to");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate From = LocalDate.parse(fromDate, formatter);
LocalDate To = LocalDate.parse(toDate, formatter);
theQuery.setParameter("from", From);
theQuery.setParameter("to", To);
Long count = (long)0;
List<EntityA> entities= theQuery.list();
for(EntityA EA:entities) {
for(EntityB EB: EA.getEntityB()) {
if(EB.someValue().equals(criteria)) count++;
}
}
return count;
Another solution I have found and is much preferred as the logic is performed by hibernate, which I have found to be a lot more faster, is to use two separate queries and utilise where :foo in elements()
Code example below (not matching question example, but idea and use of elements() should be clear)
Query<Object1> q1 = currentSession.createQuery("from Object1 o where o.objectNumber= :objectNumber");
q1.setParameter("objectNumber", objectNumber);
Object1 obj1 = q1.getSingleResult();
Query<Long> q2 = currentSession.createQuery("select count(id) from Object2 o where :object1param in elements(o.associatedObjects));
q2.setParameter("object1param ", obj1);
I want to set only fixed parameters from DTO. I have 3 Entity (tables) which are connected with FK or PK. So when I am getting data from database using ID of Table A. Jpa will give me All data with its child that's exactly I want.
public class AEntity{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "a_id")
private Long aId;
private String name;
private String model;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "b_id")
private bEntity bentity;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "c_uid")
private cEntity centity;
#JoinColumn(name = "d_id")
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<dEntity> d;
}
Like this I have B,C,D Entity also. now here I am getting Data by A_id. And created aDTO like:
public class aDTO {
private Long a_Id;
#JsonProperty(required=true)
private String name;
#JsonProperty(required=true)
private String model;
#Valid
private List<AllbDTO> bList;
private List<AllcDTO> cList;
}
And using this code:
{
Type targetListType = new TypeToken<List<aDTO>>() {
}.getType();
List<aDTO> aDTOs = mapper.map(AllDAtaByQuery, targetListType);
return aDTOs ;
}
now "AllDAtaByQuery" its an entity object to storedata from query and all are working fine. Its giving me list of a then inner list with b tables data and so on with all field.
Q: Is it possible that I tell mapper to map specific fields i want So i can send response with specific field?
Like b table have 6 fields but I want only 2 fields data in response by using same DTO. so i will use same dto for all other query and manipulate the response according need.