Spring Data Specification orderBy subquery - spring-data-jpa

On my MySql project I got this particular model with 3 entities: Prodotto with many childs QuotaIngrediente, that in turn is Many-to-One child of Ingrediente too. All my relationships are bi-directional.
All of them got an autogenerated integer Id and other fields removed to focus on the interesting ones.
#Entity
public class Prodotto {
private List<QuotaIngrediente> listaQuoteIng = new ArrayList<QuotaIngrediente>();
#OneToMany(mappedBy = "prodotto", cascade = CascadeType.ALL, orphanRemoval = true)
public List<QuotaIngrediente> getListaQuoteIng() {
return listaQuoteIng;
}
#Entity
public class QuotaIngrediente{
private Prodotto prodotto;
private Ingrediente ing;
private Double perc_ing;
#ManyToOne
#JoinColumn(name = "prodotto")
public Prodotto getProdotto() {
return prodotto;
}
#ManyToOne
#JoinColumn(name = "ing")
public Ingrediente getIng() {
return ing;
}
#Entity
public class Ingrediente {
private Set<QuotaIngrediente> quoteIng = new HashSet<QuotaIngrediente>();
#OneToMany(mappedBy = "ing", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<QuotaIngrediente> getQuoteIng() {
return quoteIng;
}
I'm using SpringData Specification and I can build a query to get Prodotto based on Ingrediente criteria, this way:
public static Specification<Prodotto> getProdottoByIngSpec (String ing) {
if (ing != null) {
return (root, query, criteriaBuilder) -> {
query.groupBy(root.get(Prodotto_.id));
return criteriaBuilder.like(((root.join(Prodotto_.listaQuoteIng))
.join(QuotaIngrediente_.ing))
.get(Ingrediente_.nome), "%"+ing+"%");
};
It works as expected, but now I want to sort it by the QuotaIngrediente perc_ing field OF THAT SPECIFIC INGREDIENTE.
Obviously I'm asking how to do it on DB, not in business logic.

I was struggling with a false problem due to a wrong assumption of mine. Solution was the simplest. Just sort by orderBy CriteriaQuery method. The query I used to search already filtered the QuotaIngrediente returning just the lines that match my search criteria. Then this is the only line I had to add to my Specification:
query.orderBy(builder.desc((root.join(Prodotto_.listaQuoteIng))
.get(QuotaIngrediente_.perc_ing)));

Related

How to Select Only Specific Children in One to Many Relation in JPA

Here below is a simple model for a pet shop...
Pet Class
#Entity
#Table(name = "pet")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public abstract class Pet {
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "birth_date", nullable = false)
private LocalDate birthDate;
#Column(name = "death_date")
private LocalDate deathDate;
#ManyToOne
#JoinColumn(name = "pet_shop_id", nullable = false, referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private PetShop petShop;
public void setPetShop(PetShop petShop) {
setPetShop(petShop, true);
}
public void setPetShop(PetShop petShop, boolean add) {
this.petShop= petShop;
if (petShop!= null && add) {
petShop.addPet(this, false);
}
}
PetShop Class
#Entity
#Table(name = "pet_shop")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class PetShop {
#Column(name = "id", nullable = false)
private Long id;
...
#OneToMany(
mappedBy = "petShop",
fetch = FetchType.LAZY,
cascade = {CascadeType.ALL})
private List<Pet> pets= new ArrayList<>();
public void addPet(final Pet pet) {
addPet(pet, true);
}
public void addPet(final Pet pet, boolean set) {
if (pet!= null) {
if (pets.contains(pet)) {
pets.set(pets.indexOf(pet), pet);
} else {
pets.add(pet);
}
if (set) {
pet.setPetShop(this, false);
}
}
}
}
PetShopRepository Interface
public interface PetShopRepository
extends JpaRepository<PetShop, Long> {
#Query(
"SELECT DISTINCT ps FROM PetShop ps"
+ " JOIN ps.pets p"
+ " WHERE ps.id = :id AND p.deathDate IS NULL")
#Override
Optional<PetShop> findById(#NonNull Long id);
}
... and here is how to create a PetShop with 2 Pet instances (one alive and another one dead):
final Pet alive = new Pet();
alive.setName("cat");
alive.setCall("meow");
alive.setBirthDate(LocalDate.now());
final Pet dead = new Pet();
dead.setName("cat");
dead.setCall("meow");
dead.setBirthDate(LocalDate.now().minusYears(15L));
dead.setDeathDate(LocalDate.now());
final PetShop petShop = new PetShop();
petShop.getPets().add(alive);
petShop.getPets().add(dead);
petShopRepositiry.save(petShop);
Now I want to retrieve the PetShop and I'd assume it contains only pets that are alive:
final PetShop petShop = petShopRepository.findById(shopId)
.orElseThrow(() -> new ShopNotFoundException(shopId));
final int petCount = petShop.getPets().size(); // expected 1, but is 2
According to my custom query in PetShopRepository I'd expect petShop.getPets() returns a list with 1 element, but it actually returns a list with 2 elements (it includes also the dead pet).
Am I missing something? Any hint would be really appreciated :-)
This is because Jpa maintains the coherence of the relations despite your query.
I.e. : your query returns the shops having at least one pet alive. But, Jpa will return the shop with the complete set of pets. And you can probably see extra sql queries sent by Jpa (if you set show_sql=true) to refill pets collection on the returned shop.
Fundamently, it's not because you wanted to get the shops with living pets that these shops loose their dead pets.
To get it right you would have to design the pets collection so that it would filter the dead pets. Hibernate provides such annotations (#Filter and #FilterDef), but apparently JPA does not.
I don't think that filtering at #Postload would be a good idea, because you would have to put back the filtered dead pets in the collection before any flush in the database. That looks risky to me.

Cannot delete or update a parent row: a foreign key constraint fails Spring JPA

I have this query
DELETE
FROM bookings as b
WHERE b.check_out = CURRENT_DATE;
and I get
Cannot delete or update a parent row: a foreign key constraint fails (online_booking_app.booked_rooms, CONSTRAINT FK3x1lpikb2vk75nx41lxhdicvn FOREIGN KEY (booking_id) REFERENCES bookings (id))
My Booking entity has CascadeType.ALL and mapped by matches the other side - from my research these are some of the mistakes that could lead to this message.
Here is the BookingEntity:
#Entity
#Table(name = "bookings")
public class BookingEntity extends BaseEntity {
#OneToMany(mappedBy = "booking",cascade = CascadeType.ALL, orphanRemoval = true)
private List<BookedRoomsEntity> bookedRooms = new ArrayList<>();
private String firstName;
private String lastName;
public List<BookedRoomsEntity> getBookedRooms() {
return bookedRooms;
}
public BookingEntity setBookedRooms(List<BookedRoomsEntity> bookedRooms) {
this.bookedRooms = bookedRooms;
return this;
}
BookedRoomsEntity
#Entity
#Table(name = "booked_rooms")
public class BookedRoomsEntity extends BaseEntity {
#ManyToOne()
private BookingEntity booking;
public BookingEntity getBooking() {
return booking;
}
public BookedRoomsEntity setBooking(BookingEntity booking) {
this.booking = booking;
return this;
}
The CascadeType does only apply to EntityManager operations.
You therefore have two options:
Load the entities to be deleted first and then use EntityManager.remove
Remove the referencing entities first with a separate JPQL statement.

Spring JPA query using specification and projection

I used spring jpa specification to build dynamically an entity query.
It's working perfect but the query returns all entity fields which makes the performance slower.
I want to fetch specific entity fields only and not fetching all entity fields and dependencies which I don't want and I will not use.
I search on the web, I tried some scenarios but without any lack.
Can anyone suggest any solution on this?
Thanks in advance
Here is what I have.I'm using spring boot 2.2.4
public class Concert {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#Column
private String code;
#Column
private double totalIncome;
#Column
private double totalExpenses;
#Column
private double totalBudget;
#ManyToOne(targetEntity = Orchestra.class, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "orchestra_id")
private Orchestra orchestra;
#ManyToOne(targetEntity = ConcertStatus.class, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "concert_status_id")
private ConcertStatus status;
/* other fields */
}
Specification:
public class ConcertSpecification implements Specification<Concert> {
#Override
public Predicate toPredicate(Root<Concert> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
//add add criteria to predicates
for (Criterion criteria : criteriaList) {
/* predicates builder here */
}
return builder.and(predicates.toArray(new Predicate[0]));
}
}
Repository:
public interface ConcertDao extends JpaRepository<Concert, Long>, JpaSpecificationExecutor<Concert>, PagingAndSortingRepository<Concert, Long> { }
ConcertService:
public interface ConcertService {
Page<Concert> findAll(#Nullable Specification<Concert> spec, Pageable pageable);
}
ConcertServiceImpl:
#Service(value = "concertService")
public class ConcertServiceImpl implements ConcertService {
public Page<Concert> findAll(#Nullable Specification<Concert> spec, Pageable pageable){
List<Concert> list = new ArrayList<>();
concertDao.findAll(spec).iterator().forEachRemaining(list::add);
return new PageImpl<Concert>(list);
}
}
Usage of projections with specifications are not supported and there is a PR for it that has been hanging for over five years.

Why am I getting "Duplicate entry" errors for related objects upon merge in eclipselink?

I have an entity class that contains a map of key-value pairs which live in a different table and there may be no such pairs for a given entity. The relevant code for the entity classes is below.
Now, when I insert such an entity with persist(), then add key-value pairs, and then save it with merge(), I get duplicate entry errors for the related table that stores the key-value pairs. I tried to hold back insertion until the keys were added, to have one call to persist() only. This led to duplicate entry errors containing an empty (zero) id in the foreign key column (ixSource).
I followed the process in the debugger, and found that eclipselink seems to be confused about the cascading. While it is updating the entity, it executes calls that update the related table. Nonetheless, it also adds those operations to a queue that is processed afterwards, which is when the duplicate entry errors occur. I have tried CascadeType.ALL and MERGE, with no difference.
I'm using static weaving, if it matters.
Here's the entities`code, shortened for brevity:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "sType")
#Table(name = "BaseEntity")
public abstract class BaseEntity extends AbstractModel
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ix")
private long _ix;
}
#Entity
#Table(name = "Source")
public class Source extends BaseEntity
{
#OneToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "SourceProperty", joinColumns = { #JoinColumn(name = "ixSource") })
#MapKey(name = "sKey")
private Map<String, SourceProperty> _mpKeys;
// ... there's more columns that probably don't matter ...
}
#Entity
#Table(name = "SourceProperty")
#IdClass(SourcePropertyKey.class)
public class SourceProperty
{
#Id
#Column(name = "sKey", nullable = false)
public String sKey;
#Id
#Column(name = "ixSource", nullable = false)
public long ixSource;
#Column(name = "sValue", nullable = true)
public String sValue;
}
public class SourcePropertyKey implements Serializable
{
private final static long serialVersionUID = 1L;
public String sKey;
public long ixSource;
#Override
public boolean equals(Object obj)
{
if (obj instanceof SourcePropertyKey) {
return this.sKey.equals(((SourcePropertyKey) obj).sKey)
&& this.ixSource == ((SourcePropertyKey) obj).ixSource;
} else {
return false;
}
}
}
I can't see how those errors would occur. Could you include the SQL and ful exception.
What version of EclipseLink are you using, did you try the latest release?
Why are you calling merge? Are you detaching the objects through serialization, if it is the same object, you do not need to call merge.
It could be an issue with the #MapKey, does it work if you remove this?

How to correctly do a manytomany join table in JPA?

I need 3 entities: User, Contract (which are a many to many relation) and a middle entity: UserContract (this is needed to store some fields).
What I want to know is the correct way to define the relationships between these entities in JPA/EJB 3.0 so that the operations (persist, delete, etc) are OK.
For example, I want to create a User and its contracts and persist them in a easy way.
Currently what I have is this:
In User.java:
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<UserContract> userContract;
In Contract.java:
#OneToMany(mappedBy = "contract", fetch = FetchType.LAZY)
private Collection<UserContract> userContract;
And my UserContract.java:
#Entity
public class UserContract {
#EmbeddedId
private UserContractPK userContractPK;
#ManyToOne(optional = false)
private User user;
#ManyToOne(optional = false)
private Contract contract;
And my UserContractPK:
#Embeddable
public class UserContractPK implements Serializable {
#Column(nullable = false)
private long idContract;
#Column(nullable = false)
private String email;
Is this the best way to achieve my goals?
Everything looks right. My advice is to use #MappedSuperclass on top of #EmbeddedId:
#MappedSuperclass
public abstract class ModelBaseRelationship implements Serializable {
#Embeddable
public static class Id implements Serializable {
public Long entityId1;
public Long entityId2;
#Column(name = "ENTITY1_ID")
public Long getEntityId1() {
return entityId1;
}
#Column(name = "ENTITY2_ID")
public Long getEntityId2() {
return entityId2;
}
public Id() {
}
public Id(Long entityId1, Long entityId2) {
this.entityId1 = entityId1;
this.entityId2 = entityId2;
}
}
protected Id id = new Id();
#EmbeddedId
public Id getId() {
return id;
}
protected void setId(Id theId) {
id = theId;
}
}
I omitted obvious constructors/setters for readability. Then you can define UserContract as
#Entity
#AttributeOverrides( {
#AttributeOverride(name = "entityId1", column = #Column(name = "user_id")),
#AttributeOverride(name = "entityId2", column = #Column(name = "contract_id"))
})
public class UserContract extends ModelBaseRelationship {
That way you can share primary key implementation for other many-to-many join entities like UserContract.