Spring Data: findByEntityNot() in a many to many relationship - java

I have the following many to many relationship:
#Entity
public class Foo implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonIgnore
#ManyToMany
#JoinTable(name = "foo_bar",
joinColumns = {#JoinColumn(name = "foo_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "bar_name", referencedColumnName = "name")})
private Set<Bar> bars = new HashSet<>();
}
#Entity
public class Bar implements Serializable {
#Id
private String name;
}
Now I want to query the FooRepository for all Foo's which DO NOT contain a Bar with the name "example". I tried to use the following Spring data method in FooRepository:
findByBars_NameNot(String barName);
But this returns one of every entry of the pivot table foo_bar which doesn't have "example" in its bar_name column. This means it can return duplicate Foo objects as well as Foo object which do actually contain a Bar with name "example" i.e. it's equivalent to the following SQL:
SELECT * FROM myschema.foo_bar WHERE bar_name != "example";
Is there any nice way in Spring data to write a repository method to do what I am trying?
I have found the following native query which does what I need but I am hesitant to use a native query as I feel there is a cleaner way of doing this:
SELECT * FROM myschema.foo WHERE id NOT IN (SELECT foo_id FROM myschema.foo_bar WHERE bar_name = "example")

From Spring Data JPA docs
public interface FooRepository extends JpaRepository<Foo, Long> {
#Query("SELECT f FROM Foo f WHERE NOT EXISTS (SELECT b FROM f.bars b WHERE b.name = ?1)")
Foo findByNotHavingBarName(String name);
}
Unfortunately, there's no support for EXISTS queries in the query creation from method names

Related

Spring jpa fetch eager not working on findById

I m using spring and hibernate for my java object.
I have an entity like this :
#Entity
#Table
public class Function implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
/** Code value. */
private String code;
}
And another entity that reference the first one like
#Entity
#Table(name = "role", uniqueConstraints = { #UniqueConstraint(columnNames = { "id" }), #UniqueConstraint(columnNames = { "code" }) })
public class RoleDef extends CodeLabelEntity{
...some other primitive type...
/** List of functions */
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "role_function",
joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "function_code", referencedColumnName = "code"))
private Set<Function> functions;
}
I m using repository like this :
public interface RoleDefRepository extends JpaRepository<RoleDef, Long>, QuerydslPredicateExecutor<RoleDef> {
}
when i m doing in my roleDefService :
roleDefRepository.findAll();
I can iterate over RoleDef and functions beacause EAGER do the job
But when i'm trying to do:
RoleDef roleDef = roleDefRepository.findById(id).orElse(null);
My functions Set inside the roledef is empty. findById is not overided, it's the default method like findAll.
If you see something that i ommit to delcare...
I m using last version of spring/hibernate
Thanks
I resolved my problem by doing this in my repository :
#Query("select r from RoleDef r join fetch r.functions where r.id = ?1")
Optional<RoleDef> findByIdWithFunctionTree(Long id);
But if someone can find the real problem because i think EAGER on the relation or accessing by a get when transaction still open will do the same as the fetch...

How do I query a list of entities in a many to many relationship in JPA?

Suppose I have the following two entities:
#Entity
#Table(name="manifest")
public class Manifest extends DbTable implements Serializable {
public Manifest() { }
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
public Long id;
#ManyToMany(cascade=CascadeType.ALL, mappedBy="manifests",fetch= FetchType.LAZY)
public List<Thingy> thingys;
}
and
#Entity
#Table(name="thingy")
public class Thingy extends DbTable implements Serializable {
public Thingy(){}
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
public Long id;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name = "manifest_thingy",
joinColumns = #JoinColumn(name = "thingy_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name = "manifest_id", referencedColumnName="id"))
public List<Manifest> manifests;
}
How can I query my thingies that belong to a given manifest? I have tried queries like
"SELECT DISTINCT d
FROM Thingy d
WHERE :manifest MEMBER OF d.manifests"
or
"SELECT DISTINCT d
FROM Thingy d
JOIN d.manifests m
WHERE m = :manifest"
or
"SELECT DISTINCT d
FROM Thingy d
JOIN d.manifests m
WHERE m.id = :manifestId"
the latter of those three being basically the only suggestion I could find searching around for this, but to no avail. For all 3 of those I think what I'm getting is an empty list (rather than an error). The query is being fed through something like this (parameters set as appropriate):
myEntityManager
.createQuery(giantQueryStringGoesHere, Thingy.class)
.setParameter("manifest", myManifestObject)
.getResultList();
If you know the specific manifest ID couldn't you just retrieve that manifest and get the list of thingys from it?
Manifest m = em.find(Manifest.class, manifestId);
List<Thingy> thingys = m.thingys;

Hibernate/JPA JPQL to wrong SQL when querying Map<String,String> field

This is my Entity configuration
#Entity
#NamedQuery(name = "Payment.findByEmail", query = "SELECT p FROM Payment p JOIN p.additionalAuthData a " +
"WHERE KEY(a) = 'email' AND VALUE(a) = ?1 AND (p.paymentType = 4 OR p.paymentType = 10)")
public class Payment {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name = "payment_type")
private Integer paymentType;
/** other properties, getters and setters */
#ElementCollection
#CollectionTable(name = "additional_auth_data")
#MapKeyJoinColumn(name = "id", referencedColumnName = "id")
#MapKeyColumn(name = "field")
#Column(name = "data_value")
private Map<String, String> additionalAuthData;
}
The NamedQuery findByEmail("test#example.com") generates the following SQL
select -- all fields ...
from payment payment0_ inner join additional_auth_data additional1_ on payment0_.id=additional1_.id
where
additional1_.field='email' and (select additional1_.data_value from additional_auth_data additional1_ where payment0_.id=additional1_.id)='test#example.com' and (payment0_.payment_type=4 or payment0_.payment_type=10)
which is wrong: it may work if you have only one row but it blows up otherwise. H2 complains Scalar subquery contains more than one row and PostgreSQL more than one row returned by a subquery used as an expression. In fact, query's where condition compares a scalar value ('test#example.com') with a subquery.
The correct SQL should be:
select -- all fields
from payment payment0_ inner join additional_auth_data additional1_ on payment0_.id=additional1_.id
where additional1_.field='payerEmail' and additional1_.data_value='test#example.com' and (payment0_.payment_type=4 or payment0_.payment_type=10)
Is the HSQL correct? Is there a way to instruct Hibernate to generates a clever, better SQL? Is this a Hibernate bug?
Note: Hibernate shipped with Spring Boot Starter 1.3.7.RELEASE
Edit:
Using an #Embeddable class
#ElementCollection
#JoinTable(name = "additional_auth_data", joinColumns = #JoinColumn(name = "id"))
#MapKeyColumn(name = "field")
#Column(name = "data_value")
private Set<AdditionalData> additionalAuthData;
#Embeddable
public static class AdditionalData {
#Column(name = "field", nullable = false)
private String field;
#Column(name = "data_value")
private String dataValue;
protected AdditionalData() {
}
public AdditionalData(String field, String dataValue) {
this.field = field;
this.dataValue = dataValue;
}
/** Getters, setters; equals and hashCode on "field" */
}
#NamedQuery(name = "Payment.findByEmail", query = "SELECT p FROM Payment p JOIN p.additionalAuthData a " +
"WHERE a.field = 'email' AND a.dataValue = ?1 AND (p.paymentType = 4 OR p.paymentType = 10)")
solves the problem, and the SQL is correct, but it looks just plain wrong, like shooting a fly with a bazooka...
It generates correct SQL without value().
Use just a=?1
But I would expect is should generate it simple also with it.

Workaround for HHH-2772 (hibernate querydsl)

I'm trying to perform a query to find cars by their foo property. The properties are stored in a different table.
I have two classes
#Embeddable
#Table(name = "PROPERTY")
public class Property {
#Column(name = "type", nullable = false)
private String type;
#Column(name = "string_value", nullable = true)
private String stringValue;
...
}
#Entity
#Table(name = "CAR")
public class Car {
#Id
...
private String id;
#ElementCollection(fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
#CollectionTable(name = "PROPERTY", joinColumns = #JoinColumn(name = "car_ID") )
private Set<Property> properties = new HashSet<Property>();
...
}
I'm trying to perform a query
QueryDsl:
.from(car)
.leftJoin(car.properties, foo)
.on(foo.type.eq("foo"))
.where(predicate)
Resulting HQL:
select
car
from
com....Car car
left join
car.properties as foo with foo.type = :a1
where
...
This doesn't work because of: https://hibernate.atlassian.net/browse/HHH-2772
Before that, it was possible to write HQL:
SELECT cat FROM Cat cat LEFT JOIN cat.kittens as kitten WITH kitten.owner=:owner
Now the HQL is raising an exception:
org.hibernate.hql.ast.InvalidWithClauseException: with clause can only reference columns in the driving table
Workaround is to explicitly use primary key (ownerId):
SELECT cat FROM Cat cat LEFT JOIN cat.kittens as kitten WITH kitten.owner.ownerId=:ownerId
The problem is that I don't have the ownerId, or an owner, since it's an element collection.
If I were to turn the element collection into a #oneToMany #manyToOne, the property could not longer be embeddable and would require an id. This is not an option. (I can't define a composite ID (this is a requirement), and I don't want to add a new column )
What do you recommend?
Is it possible to add the Car or Car Id as a field into an embaddable class?
Can I create the criteria in a different way?
I'm interested in any workaround that doesn't require database changes. (Hibernate changes or ok)
Thank you

MEMBER OF in EJB-QL 3 doesn't work

I would like to retrieve many 'Access' which have one 'Role' in common.
It's the named query:
SELECT access
FROM Access AS access
WHERE :role MEMBER OF access.listRole
The Access entity
public class Access implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private String libelle;
#ManyToOne
private Module oneModule;
#ManyToMany
private List<Role> listRole;
/* Setter & Getter */
}
The Role entity
public class Role implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private String description;
#Enumerated(EnumType.STRING)
private Flag oneFlag;
#Transient
private int lengthAccess;
#OneToMany(mappedBy="oneRole")
private List<UserAccount> listUserAccount;
#ManyToMany
private List<Access> listAccess;
/* Geter & Setter */
}
But I don't achieve to do the right EJB-QL !
Configuration:
EJB 3
MySQL (InnoDB)
jBoss
Plop
Thanks.
I cannot reproduce the problem. When running the JPQL query you provided, like this:
String qlString = "SELECT access " +
"FROM Access AS access " +
"WHERE :role MEMBER OF access.listRole";
Role role = new Role();
role.setId(1L);
List accesses = session.createQuery(qlString).setParameter("role", role).list();
Hibernate generates the following SQL query for me (I simplified a bit your entities by removing some attributes):
select
access0_.id as id127_,
access0_.libelle as libelle127_,
access0_.name as name127_
from
Access access0_
where
? in (
select
role2_.id
from
Access_ROLES listrole1_,
ROLES role2_
where
access0_.id=listrole1_.Access_id
and listrole1_.listRole_id=role2_.id
)
Everything seems correct (tested with Hibernate Core 3.3.0.SP1, Hibernate Annotations 3.4.0.GA, Hibernate EM 3.4.0.GA)
What version of Hibernate (Core, Annotations, EntityManager) are you using exactly? What error do you get exactly? Can you show how you invoke the query?
My link #ManyToMany between my two classes isn't write in the right way, during the project's building, 2 Tables has created in MySQL ("access_role" for my link #ManyToMany in the 'access' class, and role_access for my link #ManyToMany in the 'role' class)
So, to correct this, I modified like this
public class Access implements Serializable {
// ...
#ManyToMany(mappedBy="listAccess")
private List<Role> listRole;
// ...
}
public class Role implements Serializable {
// ...
#ManyToMany
#JoinTable(name = "access_role",
joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "access_id", referencedColumnName = "id"))
private List<Access> listAccess;
// ...
}

Categories

Resources