Composite key (EmbeddedId) with JoinColumn, select - spring-boot

I have a pretty annoying use case that is a puzzle.
I have the following composite key (Embeddable):
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Getter
#Embeddable
public class DefaultFilterId implements Serializable {
#Column(name = "AUTOM_PLAN_TYPE_TCD_ID")
Long planTypeId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "FILTER_TYPE_TCD_ID", referencedColumnName = "FILTER_TYPE_TCD_ID")
FilterTypeEntity filterType;
#Column(name = "VOLGORDE")
Long order;
}
My entity looks like this:
#Entity
#Table(name = "ZVZ_PLAN_T_FLTR_T_DEF")
#AllArgsConstructor
#NoArgsConstructor
#Getter
public class DefaultFilterEntity {
#EmbeddedId
private DefaultFilterId defaultFilterId;
private Long planTypeId;
#MapsId("filterTypeId")
private Long filterType;
private Long order;
}
Basically I want a composite primary key on the 3 fields, not sure how to use mapsId on the id (Long value) of the FilterType.
The JPA query is simple:
#Repository
public interface DefaultFilterRepository extends JpaRepository<DefaultFilterEntity, Long> {
List<DefaultFilterEntity> findDefaultFiltersByPlanTypeId(Long planId);
}
It's a pain really, almost on the brink of just writing an SQL query which would take me 2 seconds :-/
Anyhow the query fails because the select query isn't correctly generated; in the hibernate log I've seen that the property names are included in the select, while they obviously don't exist.
So how do I do this?
Edit: I gave up on the convoluted embeddable shizzle.
Went with:
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Getter
public class DefaultFilterId implements Serializable {
Long planTypeId;
FilterTypeEntity filterType;
Long order;
}
#Entity
#Table(name = "ZVZ_PLAN_T_FLTR_T_DEF")
#IdClass(DefaultFilterId.class)
#AllArgsConstructor
#NoArgsConstructor
#Getter
public class DefaultFilterEntity {
#Id
#Column(name = "AUTOM_PLAN_TYPE_TCD_ID")
private Long planTypeId;
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "FILTER_TYPE_TCD_ID", referencedColumnName = "FILTER_TYPE_TCD_ID")
FilterTypeEntity filterType;
#Id
#Column(name = "VOLGORDE")
private Long order;
}
This seems to generate the correct hibernate query, however it returns an empty list from my jpa query. When performing the hibernate query directly on the db it shows the correct resultset.
(Well correct, not really, as I don't see JOIN being made to get the joined filterTypes).
Any idea?

Fixed this issue by adding a surrogate key to the table.
Hibernate is pleased, and all worked instantly.
In case devs don't have ability to add a surrogate key to the table for some reason, I wish you good luck.

Related

LazyInitializationException when get EAGER fetch object OneToOne

I have two entity
#Entity
#Table(name = "user")
#Data
#Builder
#EqualsAndHashCode(callSuper=false)
#ToString
#AllArgsConstructor
#NoArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "name")
private String name;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#PrimaryKeyJoinColumn
private UserLastLogin userLastLogin;
}
#Entity
#Table(name = "lastLogin")
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#ToString(onlyExplicitlyIncluded = true)
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class UserLastLogin implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "name")
private String userName;
#Column(name = "date")
private LocalDateTime date;
#OneToOne
#MapsId
#JoinColumn(name = "name")
private User user;
}
I use spring boot with spring data and jpa, hibernate in latest version.
In documentation is that #OneToOne is default EAGER, but when i get eager fetch object, i get lazyInitializationException when i not use #Transactional in get method. I don't understant why...
public UserDto getUser(String userName) {
var user= userRepository.getById(userName);
d.getSystemUserLastLogin(); // this throw lazy initialization exception
return mapper.entityToDto(d);
}
When i'will mark this method #Transactioal, this work. But, not recommendend used transactions in get method. I need use EAGER fetch in this relationship.
When i view query hibernate, i have one select, but children object is not available.
Hibernate:
select
user0_.name as nazwa1_4_0_,
user2_.name as name1_23_2_,
user2_.data as data3_23_2_
from
user0_
left outer join
last_login user2_
on user0_.name=user2_.name
where
user0_.name=?
The problem was that despite the fetch eager, lazy was used. This was due to the use of the getById method from the repository, which retrieves only the object's references and snaps all the fields when lazy is retrieved. Changing to findById solves the problem as findById takes an object, not a reference.
I would recommend you to use secondary tables instead like this:
#Entity
#Table(name = "user")
#Data
#Builder
#EqualsAndHashCode(callSuper=false)
#ToString
#AllArgsConstructor
#NoArgsConstructor
#SecondaryTable(name = "lastLogin", pkJoinColumns = #PrimaryKeyJoinColumn(name = "name"))
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "name")
private String name;
#Column(table = "lastLogin", name = "date")
private LocalDateTime date;
}
Also see https://www.baeldung.com/jpa-mapping-single-entity-to-multiple-tables for more details.

Embeddable is creating 2 tables jpa springs

I have below entites
AbstractOrderEntry :
#Getter #Setter
public class AbstractOrderEntry implements Serializable
{
#ElementCollection
private List<DiscountValue> discountValues = new ArrayList<>();}
}
CartEntry extending AbstractOrderEntry
#Entity
#EntityListeners(value = AuditListener.class)
#Table(name = "cartentry")
#Getter #Setter
public class CartEntry extends AbstractOrderEntry implements Serializable,Cloneable,Auditable
{
#Id
#Column(name="CartEntryID",nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
}
DiscountValueEntity:
#Embeddable
#Getter #Setter
public class DiscountValue {
private String code;
private BigDecimal value;
private BigDecimal appliedValue;
}
When I start the server it is generating to tables like
cart_discountValue
cart_discountvalue
one with camelcase and other is with lowercase.
We are also extending AbstractOrderEntry for order entity as well hence for order also 2 tables are getting created.
How can overcome this issue.Because of this issue table data is not properly persisting.
Thanks , Inadvance.
Sree

JPQL query / JPA / Spring boot best practice of updating many to many table

I have a user table and a city table and I have a connecting table users_cities (columns: id, user_id, city_id). A user can follow multiple cities.
From the client side i send an array of cityIds. Some might be new some might still be selected and some might have been deselected.
What is a best practice to update the users_cities table with the data? Should I just delete everything for that particular userId and insert the new array or ... ?~
Also, how does one delete and repsectively insert the data in bulk, for a many to many reference?!
#Getter
#Setter
#NoArgsConstructor
#ToString
#Accessors(chain = true)
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String email;
private String password;
private Boolean isGuest;
#ManyToMany(fetch = FetchType.EAGER)
private Set<Role> roles;
#ManyToOne()
#JoinColumn(name = "country_following_id")
private Country countryFollowing;
#ManyToMany()
private Set<City> citiesFollowing;
#ManyToMany()
private Set<Offer> offersFollowing;
}
and
#Getter
#Setter
#NoArgsConstructor
#ToString
#Accessors(chain = true)
#Entity
#Table(name = "cities")
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#NonNull
private Long id;
private String countryCode;
private String name;
#Latitude
private Double latitude;
#Longitude
private Double longitude;
}
Should I just delete everything for that particular userId and insert the new array
Since the relation connecting users and cities does not have an identity of its own, that sounds reasonable
Also, how does one delete and repsectively insert the data in bulk, for a many to many reference?
Just clear the User.citiesFollowing collection and populate it anew (hint: since you have cityIds at the ready, you can use EntityManager.getReference() to load the cities)

How to make composite Foreign Key part of composite Primary Key with Spring Boot - JPA

I have a problem with the historization of objects in the database.
the expected behavior of the save JpaRepository method is : Insert in the two tables idt_h and abo_h
But the current behavior is Insert in the idt_h table and update in the abo_h table.
#Data
#Entity
#Table(name = "ABO_H")
#AllArgsConstructor
#NoArgsConstructor
public class AboOP {
#Id
#Column(name = "ABO_ID")
private String id;
#Column(name = "ABO_STATUT")
private String statut;
#Column(name = "ABO_DATE_STATUT")
private Instant date;
#Column(name = "ABO_CoDE")
private String code;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "IDC_ID", referencedColumnName = "IDC_ID"),
#JoinColumn(name = "DATE_HISTO", referencedColumnName = "DATE_HISTO")
})
private IdtOP idtOP;
}
#Data
#Entity
#Table(name = "IDT_H")
#AllArgsConstructor
#NoArgsConstructor
public class IdtOP {
#AttributeOverrides({
#AttributeOverride(name = "id",
column = #Column(name = "IDC_ID")),
#AttributeOverride(name = "dateHisto",
column = #Column(name = "DATE_HISTO"))
})
#EmbeddedId
private IdGenerique idtId = new IdGenerique();
//Other fields
}
#Data
#AllArgsConstructor
#NoArgsConstructor
#Embeddable
public class IdGenerique implements Serializable {
private String id;
private Instant dateHisto;
}
I think that the class IdGenerique which groups the id and dateHisto is not well invoked for the table abo_h ??
thanks in advance
When you use the save() method, entityManager checks if the entity is new or not. If yes, the entity will be saved, if not, it'll be merged
If you implement your Entity Class with the inteface Persistable, you can override the method isNew() and make it returns True. In that case the save() method will persist, and not merge, your entity.

Jpa findAllBy* using Long id instead of an entity

I just do not want to do:
myEntity = findById(Long id)
findAllByEntityAnd*(MyEntity myEntity, *)
instead I want do:
findAllByEntityAnd*(Long entityId, *)
Is there something I missed to achieve what I wanted or I just cannot achieve it directly?
Thanks for the help ~
When I tried it, the Spring prompted me:
java.lang.IllegalArgumentException: Could not create query metamodel for method public abstract java.util.List com.worksap.morphling.raptor.dump.thread.dao.ThreadDoRepository.findAllByDumpDoAndLocksWaitingContains(java.lang.Long,java.lang.String)!
Here is my table for your reference:
#Data
#Builder
#Entity
#Table(name = "thread_info")
#AllArgsConstructor
#NoArgsConstructor
public class ThreadDo implements Serializable {
private static final long serialVersionUID = -1L;
String name;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "thread_dump_id")
#JsonIgnore
private ThreadDumpDo dumpDo;
...
}
#Data
#Builder
#Entity
#Table(name = "thread_dump")
#AllArgsConstructor
#NoArgsConstructor
public class ThreadDumpDo implements Serializable {
private static final long serialVersionUID = -1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(
mappedBy = "dumpDo",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<ThreadDo> threadDoList;
}
And then I have a ThreadDoRepository and want to locate the threadDos directly like this:
List<ThreadDo> findAllByDumpDoAndLocksWaitingContains(Long dumpId, String lockWaiting);
I can achieve it via #Query as follows, but I really think it's pretty ugly since it's easy to interpret (I think).
#Query(value = "select * from thread_info t where t.thread_dump_id = ?1 and t.locks_waiting like ?2",
nativeQuery = true)
List<ThreadDo> findAllByDumpDoAndLocksWaitingContains(Long dumpId, String lockWaiting);
Try this
List<ThreadDo> findAllByDumpDo_IdAndLocksWaitingContains(Long dumpId, String lockWaiting);

Resources