Ebean setIncludeSoftDeletes() throw SQLException - playback

I have two table with #History and #SoftDelet each. When I work with non deleted data it is fine. But when I try find a deleted data for model which contains field with EAGER loading option, using setIncludeSoftDeletes():
t2.find.query().setId(id).setIncludeSoftDeletes().findOne();
I got Exception:
Query threw SQLException:No value specified for parameter 2 Bind values:[1, ] Query was:select t0.id, t0.deleted, t1.id from t2 t0 left join t1_with_history t1 on t1.t2_id = t0.id and (t1.sys_period_start <= ? and (t1.sys_period_end is null or t1.sys_period_end > ?)) where t0.id = ?
why was added a join with view t1_with_history?
Models
#Entity
#Table(name = "t1")
#History
public class t1 extends Model {
public static final Finder<String, t1> find = new Finder<>(t1.class);
#Id
public Long id;
#SoftDelete
public Boolean deleted;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "t2_id")
private t2 _t2;
#JsonIgnore
public t2 get_t2(){
return this._t2;
}
}
#Entity
#Table(name = "t2")
#History
public class t2 extends Model {
public static final Finder<String, t2> find = new Finder<>(t2.class);
#Id
public Long id;
#SoftDelete
public Boolean deleted;
#OneToOne(mappedBy = "_t2", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public t1 _t1;
}

Related

JPA JoinTable with additional columns

Spring Boot
Spring Data
JPA Hibernate
Came across a requirement where JPA ManyToMany relationship table with an extra column. Have looked at StackOverflow and found several questions related to same requirement. Most of the answers on the forums ask for EmbeddedId field with a composite primary key with two columns. I tried the solutions from the forums, here is the code snippet.
#Data
#Entity
#Table (name = "TABLE_A")
public class TableA {
#Id
#Column (name = "table_a_id")
private Integer id;
...
#OneToMany (mappedBy = "pk.tableA")
private List<TableABMapping> mappingTable;
}
#Data
#Entity
#Table (name = "TABLE_B")
public class TableB {
#Id
#Column (name = "table_b_id")
private Integer id;
...
#OneToMany (mappedBy = "pk.tableB")
private List<TableABMapping> mappingTable;
}
#Data
#Entity
#Table (name = "TABLE_A_TABLE_B_MAPPING")
public class TableABMapping implements Serializable {
#EmbeddedId
private MappingKey pk = new MappingKey();
#Column(name = "addon_field")
private Double additionalField;
#Transient
public TableA getTableA() {
return getPk().getTableA();
}
public void setTableA(TableA tableA) {
getPk().setTableA(tableA);
}
#Transient
public TableB getTableB() {
return getPk().getTableB();
}
public void setTableB(TableB tableB) {
getPk().setTableB(tableB);
}
// equals() & hashCode() method override
}
#Data
#Embeddable
public class MappingKey implements Serializable {
#ManyToOne
#JoinColumn(name = "table_a_id", referencedColumnName = "table_a_id")
private TableA tableA;
#ManyToOne
#JoinColumn(name = "table_b_id", referencedColumnName = "table_b_id")
private TableB tableB;
// No argument constructor, two arguments constructor.
// equals() & hashCode() method override
}
Trying save operation from service class like this:
for (TableB tabB : tableA.getTableB()) {
TableABMapping abMapping = new TableABMapping();
abMapping.setTableA(tableA);
abMapping.setProduct(tabB);
abMapping.setAdditionalField(tabB.getAddonField());
if (tableA.getMappingTable() == null) {
tableA.setMappingTable(new ArrayList<TableABMapping>());
}
tableA.getMappingTable().add(abMapping);
}
TableA ta = tableARepository.save(tableA);
System.out.println("TableA.save(): " + ta);
Getting this error on save operation.
Unable to find TableABMapping with id MappingKey(tableA = TableA( ... ), tableB = TableB ( ... ))
Both the entities have proper ids at the time of saving the entity. But still it throws this error. Where I am making mistake?

Using JpaSpecificationExecutor with EntityGraph

I am using a implementation of JpaSpecificationExecutor and trying to use in the repository the #EntityGraph for select which relationships entity they get in a complex query.
My entities examples (all relationships bidireccional)
#Entity
#Table(name = "trazabilidad_contenedor")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class TrazabilidadContenedor implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "traConSeq")
#SequenceGenerator(name = "traConSeq")
private Long id;
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties(value = "trazabilidadContenedors", allowSetters = true)
private PromoProGesCodLer promoProGesCodeLer;
.
.
.
#Entity
#Table( name = "promo_pro_ges_cod_ler")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class PromoProGesCodLer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties(value = "promoProGesCodLers", allowSetters = true)
private ProGesCodLer procesoGestoraCodLer;
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties(value = "promoProGesCodLers", allowSetters = true)
private Promocion promocion;
.
.
.
#Entity
#Table(name = "promocion")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Promocion implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
.
.
.
#Entity
#Table(name = "pro_ges_cod_ler")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#NextProGesCodLer
public class ProGesCodLer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "pgclSeq")
#SequenceGenerator(name = "pgclSeq")
private Long id;
#OneToMany(mappedBy = "procesoGestoraCodLer")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<PromoProGesCodLer> promoProGesCodLers = new HashSet<>();
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties(value = "proGesCodLers", allowSetters = true)
private ProcesoGestora procesoGestora;
.
.
.
And this is my repository
#Repository
public interface TrazabilidadContenedorRepository
extends JpaRepository<TrazabilidadContenedor, Long>, JpaSpecificationExecutor<TrazabilidadContenedor> {
#EntityGraph (
type = EntityGraph.EntityGraphType.FETCH,
attributePaths = {
"promoProGesCodeLer",
"promoProGesCodeLer.promocion",
"promoProGesCodeLer.promocion.direccion",
"promoProGesCodeLer.promocion.direccion.municipio",
"promoProGesCodeLer.procesoGestoraCodLer.procesoGestora",
"promoProGesCodeLer.procesoGestoraCodLer.codLER",
"promoProGesCodeLer.procesoGestoraCodLer.codLER.lerType",
"promoProGesCodeLer.procesoGestoraCodLer.nextProGesCodLer",
"promoProGesCodeLer.procesoGestoraCodLer.procesoGestora",
"promoProGesCodeLer.procesoGestoraCodLer.procesoGestora.gestora",
}
)
List<TrazabilidadContenedor> findAll(Specification<TrazabilidadContenedor> var1);
}
The constructor of my Specification‹TrazabilidadContenedor›
protected Specification<TrazabilidadContenedor> createSpecification(TrazabilidadContenedorCriteria criteria) {
Specification<TrazabilidadContenedor> specification = Specification.where(null);
if (criteria != null) {
if (criteria.getPromocionId() != null) {
specification =
specification.and((root, query, builder) ->
builder.equal(
root
.join(TrazabilidadContenedor_.promoProGesCodeLer, JoinType.LEFT)
.join(PromoProGesCodLer_.promocion, JoinType.LEFT)
.get(Promocion_.id),
criteria.getPromocionId()
)
);
}
if (criteria.getGestoraId() != null) {
specification =
specification.and(
(root, query, builder) ->
builder.equal(
root
.join(TrazabilidadContenedor_.promoProGesCodeLer, JoinType.LEFT)
.join(PromoProGesCodLer_.procesoGestoraCodLer, JoinType.LEFT)
.join(ProGesCodLer_.procesoGestora, JoinType.LEFT)
.join(ProcesoGestora_.gestora, JoinType.LEFT)
.get(Gestora_.id),
criteria.getGestoraId()
)
);
}
}
return specification;
}
When i have only one criteria , criteria.getPromocionId() or criteria.getGestoraId() it's OK , but if i use both at the same time i obtain.
Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias2,role=com.cocircular.greenadvisor.domain.PromoProGesCodLer.promocion,tableName=promocion,tableAlias=promocion2_,origin=promo_pro_ges_cod_ler promoproge1_,columns={promoproge1_.promocion_id,className=com.cocircular.greenadvisor.domain.Promocion}}] [select generatedAlias0 from com.cocircular.greenadvisor.domain.TrazabilidadContenedor as generatedAlias0 inner join generatedAlias0.promoProGesCodeLer as generatedAlias1 inner join generatedAlias1.promocion as generatedAlias2 inner join generatedAlias0.promoProGesCodeLer as generatedAlias3 inner join generatedAlias3.procesoGestoraCodLer as generatedAlias4 inner join generatedAlias4.procesoGestora as generatedAlias5 inner join generatedAlias5.gestora as generatedAlias6 where ( generatedAlias0.traceabilityStatus=:param0 ) and ( ( generatedAlias6.id=75304L ) and ( generatedAlias2.id=86754L ) )]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:138)
at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1542)
at org.hibernate.query.Query.getResultList(Query.java:165)
at org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter.getResultList(CriteriaQueryTypeQueryAdapter.java:76)
For this i'm usign Hibernate 5.4.15 and Spring-Boot 2.2.7.RELEASE.
Every item, presented in generated sql is bound to be present in graph.
So, let's write a full graph path:
select generatedAlias0
from com.cocircular.greenadvisor.domain.TrazabilidadContenedor as generatedAlias0
inner join generatedAlias0.promoProGesCodeLer as generatedAlias1 ---> promoProGesCodeLer
inner join generatedAlias1.promocion as generatedAlias2 ---> promoProGesCodeLer.promocion
inner join generatedAlias0.promoProGesCodeLer as generatedAlias3 ---> promoProGesCodeLer
inner join generatedAlias3.procesoGestoraCodLer as generatedAlias4 ---> promoProGesCodeLer.procesoGestoraCodLer
inner join generatedAlias4.procesoGestora as generatedAlias5 ---> promoProGesCodeLer.procesoGestoraCodLer.procesoGestora
inner join generatedAlias5.gestora as generatedAlias6 ----> promoProGesCodeLer.procesoGestoraCodLer.procesoGestora.gestora
where ( generatedAlias0.traceabilityStatus=:param0 )
and ( ( generatedAlias6.id=75304L ) and ( generatedAlias2.id=86754L ) )
Here's the provided graph:
attributePaths = {
"promoProGesCodeLer",
"promoProGesCodeLer.promocion",
"promoProGesCodeLer.promocion.direccion",
"promoProGesCodeLer.promocion.direccion.municipio",
"promoProGesCodeLer.procesoGestoraCodLer.procesoGestora",
"promoProGesCodeLer.procesoGestoraCodLer.codLER",
"promoProGesCodeLer.procesoGestoraCodLer.codLER.lerType",
"promoProGesCodeLer.procesoGestoraCodLer.nextProGesCodLer",
"promoProGesCodeLer.procesoGestoraCodLer.procesoGestora",
"promoProGesCodeLer.procesoGestoraCodLer.procesoGestora.gestora" }
Looks to me, that node promoProGesCodeLer.procesoGestoraCodLer is missing from graph

Specification API/Criteria API - Group by data returned by existing specification object and findAll(specification,pageable)

Below are my entities:
Product
#Entity
#Table(name = "Product")
public class Product extends ReusableFields
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
Long productId;
#NonNull
#Column(name = "product_name")
String productName;
String measurementUnit;
//more fields and getters setters
}
Inward Outward List related to product:
#Entity
#Table(name = "inward_outward_entries")
public class InwardOutwardList extends ReusableFields
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long entryid;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "productId", nullable = false)
#JsonIgnoreProperties(
{ "hibernateLazyInitializer", "handler" })
Product product;
#JsonSerialize(using = DoubleTwoDigitDecimalSerializer.class)
Double quantity;
//more fields
}
Inward Inventory:
#Entity
#Table(name = "inward_inventory")
public class InwardInventory extends ReusableFields implements Cloneable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "inwardid")
Long inwardid;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "inwardinventory_entry", joinColumns =
{ #JoinColumn(name = "inwardid", referencedColumnName = "inwardid") }, inverseJoinColumns =
{ #JoinColumn(name = "entryId", referencedColumnName = "entryId") })
Set<InwardOutwardList> inwardOutwardList = new HashSet<>();
//more fields
}
Inward Inventory Repo:
#Repository
public interface InwardInventoryRepo extends extends JpaRepository<InwardInventory, Long>, JpaSpecificationExecutor<InwardInventory> ,PagingAndSortingRepository<InwardInventory, Long>
{}
Previously the requirement was only to filter data and show as pages based on filters selected by user. So I have a working code to create specification dynamically based on inputs. It is working fine. After creating the specification, I am using:
Page<T> findAll(#Nullable Specification<T> spec, Pageable pageable);
to generate required list of records.
But, now a new requirement has been added to show sum of quantities grouped on product name and measurement unit. i.e. whatever data is returned after filter should be grouped by. Since the filtration logic is already working fine, I do not want to touch it.
Can somehow help how to reuse existing specification object and group the data returned by findall(specification,pageable) method.
What I already tried.
Since specification directly do not support group by, I autowired entity manager and created own criteria query. But this is not giving correct results as all the tables are getting joined twice. Might be because they are joined first during specification object and again during grouping by:
#Service
#Transactional
public class GroupBySpecification {
#Autowired
EntityManager entityManager;
Logger log = LoggerFactory.getLogger(GroupBySpecification.class);
public List<ProductGroupedDAO> findDataByConfiguration(Specification<InwardInventory> spec) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ProductGroupedDAO> query = builder.createQuery(ProductGroupedDAO.class);
Root<T> root = query.from(InwardInventory.class);
Predicate p = spec.toPredicate(root, query, builder);
query.where(p);
Join< InwardInventory, InwardOutwardList> ioList = root.join(InwardInventory_.INWARD_OUTWARD_LIST);
Join<InwardOutwardList, Product> productList = ioList.join(InwardOutwardList_.PRODUCT);
query.multiselect(productList.get(Product_.PRODUCT_NAME), productList.get(Product_.MEASUREMENT_UNIT),
builder.sum(ioList.get(InwardOutwardList_.QUANTITY)));
query.groupBy(productList.get(Product_.PRODUCT_NAME), productList.get(Product_.MEASUREMENT_UNIT));
List<ProductGroupedDAO> groupedData = fetchData(query);
return groupedData;
}
Generated SQL - all the tables joined twice
SELECT DISTINCT product7_.product_name AS col_0_0_,
product10_.measurementunit AS col_1_0_,
Sum(inwardoutw12_.quantity) AS col_2_0_
FROM inward_inventory inwardinve0_
INNER JOIN inwardinventory_entry inwardoutw1_
ON inwardinve0_.inwardid = inwardoutw1_.inwardid
INNER JOIN inward_outward_entries inwardoutw2_
ON inwardoutw1_.entryid = inwardoutw2_.entryid
AND ( inwardoutw2_.is_deleted = 'false' )
INNER JOIN product product3_
ON inwardoutw2_.productid = product3_.productid
INNER JOIN warehouse warehouse4_
ON inwardinve0_.warehouse_id = warehouse4_.warehouse_id
INNER JOIN inwardinventory_entry inwardoutw5_
ON inwardinve0_.inwardid = inwardoutw5_.inwardid
INNER JOIN inward_outward_entries inwardoutw6_
ON inwardoutw5_.entryid = inwardoutw6_.entryid
AND ( inwardoutw6_.is_deleted = 'false' )
INNER JOIN product product7_
ON inwardoutw6_.productid = product7_.productid
INNER JOIN inwardinventory_entry inwardoutw8_
ON inwardinve0_.inwardid = inwardoutw8_.inwardid
INNER JOIN inward_outward_entries inwardoutw9_
ON inwardoutw8_.entryid = inwardoutw9_.entryid
AND ( inwardoutw9_.is_deleted = 'false' )
INNER JOIN product product10_
ON inwardoutw9_.productid = product10_.productid
INNER JOIN inwardinventory_entry inwardoutw11_
ON inwardinve0_.inwardid = inwardoutw11_.inwardid
INNER JOIN inward_outward_entries inwardoutw12_
ON inwardoutw11_.entryid = inwardoutw12_.entryid
AND ( inwardoutw12_.is_deleted = 'false' )
WHERE ( inwardinve0_.is_deleted = 'false' )
AND ( warehouse4_.warehousename LIKE ? )
AND ( product3_.product_name IN ( ?, ?, ?, ? ) )
GROUP BY product7_.product_name,
product10_.measurementunit
You will have to use the existing joins that are created by the specifications. You will probably have to do something like this:
public List<ProductGroupedDAO> findDataByConfiguration(Specification<InwardInventory> spec) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ProductGroupedDAO> query = builder.createQuery(ProductGroupedDAO.class);
Root<T> root = query.from(InwardInventory.class);
Predicate p = spec.toPredicate(root, query, builder);
query.where(p);
Join< InwardInventory, InwardOutwardList> ioList = getOrCreateJoin(root, InwardInventory_.INWARD_OUTWARD_LIST);
Join<InwardOutwardList, Product> productList = getOrCreateJoin(ioList, InwardOutwardList_.PRODUCT);
query.multiselect(productList.get(Product_.PRODUCT_NAME), productList.get(Product_.MEASUREMENT_UNIT),
builder.sum(ioList.get(InwardOutwardList_.QUANTITY)));
query.groupBy(productList.get(Product_.PRODUCT_NAME), productList.get(Product_.MEASUREMENT_UNIT));
List<ProductGroupedDAO> groupedData = fetchData(query);
return groupedData;
}
<X, T> Join<X, T> getOrCreateJoin(From<?, X> from, SingularAttribute<X, T> attr) {
return from.getJoins().stream().filter(j -> j.getAttribute() == attr).findFirst().orElse(() -> from.join(attr));
}
<X, T> Join<X, T> getOrCreateJoin(From<?, X> from, PluralAttribute<X, ?, T> attr) {
return from.getJoins().stream().filter(j -> j.getAttribute() == attr).findFirst().orElse(() -> from.join(attr));
}

How to get data from tables in spring data jpa?

I have two tables
#Entity
#Table(name = "TAX_CATEGORY")
public class TaxCategory {
#Id
#GeneratedValue
#Column(name = "ID", nullable = false)
private long id;
#Column(name = "CATEGORY", nullable = false)
private String category;
#Column(name = "TAX", nullable = false)
private Double tax;
#Entity
#Table(name = "PRODUCT")
public class Product {
#Id
#GeneratedValue
#Column(name = "ID", nullable = false)
private long id;
#Column(name = "PRICE", nullable = false)
private Double price;
#Column(name = "NAME", nullable = false)
private String name;
#OneToOne
#JoinColumn(name = "TAX_CATEGORY_ID")
private TaxCategory taxCategory;
Now I want to query
"Select p.name, p.price, t.tax from Product p, TaxCategory t join p.taxCategory.id=t.id"
So List it would return is
ProductName ProductPrice Tax
but I am not able to get this data from two tables. Single table data is working fine.
public interface CustomRepositoryCustom {
public void customMethod();
}
public interface CustomRepository
extends JpaRepository<Account, Long>, CustomRepositoryCustom { }
public class CustomRepositoryImpl implements CustomRepositoryCustom {
public void customMethod() {
Query nativeQuery = entityManager.createNativeQuery("Select p.name, p.price, t.tax from Product p, TaxCategory t join p.taxCategory.id=t.id");
return query.getResultList();
}
}
This throws exception that object is not managed bean. If I create custom object then also it gives similar type of issues.
Use the following JPA query to get the both tables data. Here used jpa query to fetch the product. From product object, can get the taxCategory.
public interface CustomRepository extends JpaRepository<Account, Long>, CustomRepositoryCustom {
Query("select product from Product as product join fetch product.taxCategory as taxCategory where taxCategory.id = :taxCategoryId")
public Product getProductByCategory(#Param Long taxCategoryId);
}
Instead of query method you can directly define JPA method to find products based on category Id as.
#Repository
#RepositoryRestResource
public interface ICountryRepository extends JpaRepository<Product , Long > {
List<Product> findByTaxCategory_Id(#Param Long Id);
}

JPA2 Criteria-API: select... in (select from where)

I have the following database model:
A
aId
AB
aId
bId
B
bId
status
In a Spring data Specification, I want to return the instances of A when B.status is 'X'.
The JPQL code is the following:
select a from A a where a in
(select ab.id.a from AB ab where ab.id.b.status= :status)
These are the model classes:
#Entity
public class A {
private Long aId;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.a")
private Set<AB> ab;
}
#Entity
public class B {
private Long bId;
private String Status;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.b")
private Set<AB> ab;
}
#Entity
public class AB {
private ABPK id;
}
public class ABPK {
#ManyToOne
#JoinColumn(name="aId")
private A a;
#ManyToOne
#JoinColumn(name="bId")
private B b;
}
How would be the JPA Criteria in the Spring Specification?
public class ASpecifications {
public static Specification<A> test(final String status) {
return new Specification<Party>() {
#Override
public Predicate toPredicate(Root<A> a, CriteriaQuery<?> query, CriteriaBuilder cb) {
return null;
}
};
}
}
The Specification that returns instances of A using Criteria API is the following:
public class ASpecifications {
public static Specification<A> test(final String status) {
return new Specification<Party>() {
#Override
public Predicate toPredicate(Root<A> a, CriteriaQuery<?> query, CriteriaBuilder cb) {
Subquery<A> sq = query.subquery(A.class);
Root<AB> ab = sq.from(AB.class);
sq.select(ab.get(AB_.id).get(ABPK_.a));
sq.where(cb.equal(ab.get(AB_.id).get(ABPK_.b).get(B_.status), status));
Predicate p = cb.in(a).value(sq);
return cb.and(p);
}
};
}
}
There are some good examples included on a previous post that address exactly what you're trying to accomplish here: jpa-2-0-criteria-api-subqueries-in-expressions.
I suppose you wanted to select "A entities from AB entities where B is of provided status":
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<A> cq = cb.createQuery(A.class);
Root<AB> ab = cq.from(AB.class);
cq.select(ab.get("id").get("a"));
cq.where(cb.equal(ab.get("id").get("b.status"), status));
Select In - is an option but you could achieve same result with double join. And its easier to make such a jpa specification:
SELECT A.ID FROM A LEFT JOIN AB ON A.ID = AB.A_ID LEFT JOIN B ON AB.B_ID = B.ID WHERE B.STATUS = 'STATUS'
Method would look like this:
public static Specification<A> findB(String input) {
return new Specification<A>() {
#Override
public Predicate toPredicate(Root<A> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
Join<A,AB> AjoinAB = root.joinList(A_.AB_LIST,JoinType.LEFT);
Join<AB,B> ABjoinB = AjoinAB.join(AB_.B,JoinType.LEFT);
return cb.equal(ABjoinB.get(B_.NAME),input);
}
};
}
Or shorter
public static Specification<A> findB(String input) {
return (Specification<A>) (root, cq, cb) -> {
Join<A,AB> AjoinAB = root.joinList(A_.AB_LIST,JoinType.LEFT);
Join<AB,B> ABjoinB = AjoinAB.join(AB_.B,JoinType.LEFT);
return cb.equal(ABjoinB.get(B_.NAME),input);
};
}
I know it's been a long time since this question arose, but I came across it when I tried to do the same. This is my solution that works and I hope it helps someone.
Entities below
#Entity
public class A {
#Id
private Long id;
private String name;
#OneToMany(mappedBy = "a")
private List<AB> abList;
}
#Entity
public class B {
#Id
private Long id;
private String status;
#OneToMany(mappedBy = "b")
private List<AB> abList;
}
#Entity
public class AB {
#Id
private Long id;
private String name;
#ManyToOne
#JoinColumn(name = "a_id")
private A a;
#ManyToOne
#JoinColumn(name = "b_id")
private B b;
}

Resources