Spring data CrudRepository findBy relationships - java

I want to select all Foo entities that have any Bars that have status new.
Here's what I tried:
#Entity
#Table(name = "FOO")
class Foo {
#Id
#Column(name = "ID")
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#OneToMany
private Set<Bar> bars;
//...
}
public interface FooRepository extends CrudRepository<Foo, Long> {
#Query("select m from Foo f where f.bars.status = 'NEW' ")
public Page<Foo> findByBarStatus(Pageable pageable);
}
But I get :
org.hibernate.exception.SQLGrammarException: could not prepare statement
I also tried writing join statement instead:
select f from Foo f inner join f.bars b where b.status = 'NEW'

SELECT f FROM Foo AS f JOIN f.bars AS b WHERE b.status = 'NEW'

I think you're missing something like this
#OneToMany(mappedBy = "foo")
private Set<Bar> bars;

joining for one-to-many relationship can sometimes be tricky. (There is another answer doing it right, that you can refer to)
In this case, you may do it in reverse (assuming you have the relationship being bi-directional), by looking up from the Bar side:
select bar.foo from Bar bar where bar.status = 'NEW'

Related

Spring JPA: How to join with fields that don't exist in the entity

We have 3 entities A, B, and C. We are searching in table A with fields within B and C. And the mapping is unidirectional, so B and Collection is not defined in A.
We are using Spring Specification, how can we perform join in this case without converting the mapping to bidirectional?
#Entity
public class A {
#Id
Long id
String name
}
#Entity
public class B {
#Id
Long id
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "a_id")
A a;
#Column
String country;
}
#Entity
public class C {
#Id
Long id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "a_id")
private A a;
}
For example, if B is defined in A like that
#Entity
public class A {
#Id
Long id
String name
#OneToOne(mappedBy = "request")
private B b;
}
the join will be like that
public static Specification<A> countryEquals(String country) {
return (root, query, builder) -> builder.equal(
root.join("b", JoinType.LEFT).get("country"), country);
}
But B is not defined in A, then how to apply the join?
you can try to join it by B itself, you create a new Root and image you add a where b.a_id = a.id to your query or here, you CriteriaBuilder, then you can add more predicate to look for fields in B table. Something like this, somewhat pseudo code cause I don't have my code here atm and write it down by my memory, but I hope it can help
public static Specification<A> countryEquals(String country) {
return (root, query, builder) -> (
Root<B> b_root = query.from(B.class);
Predicate b_join = builder.equal(b_root.get("a_id"), root.get("id"))
Predicate b_country = builder.equal(b_root.get("country"), country) // or like with `%country%`
return builder.and(other_predicate,...., b_join, b_country)
)
}

Using arbitrary query as projection in spring data rest project

How it is possible to use arbitrary sql query (I mean native sql query) in some repository? My actual problem is this:
#Data //lombok thing
#Entity
public class A extends AuditModel {
private long id;
private String name;
#OneToMany(mappedBy="a") //Comments.a is owning side of association, i.e. comments table does have column called a_id as foreign key
#ToString.Exclude
private Set<Comments> comments = new HashSet();
#OneToMany(mappedBy="a") //SimpleFile.a is owning side of association
private Set<SimpleFile> comments = new HashSet();
}
Than I have my repository, which exposes nice crud interface using HAL+json representation. I am trying to enrich it with some projection/view particularly due to web UI to load one page data in single request. I am aware of excerps and projections, but they seems not to be enough powerful.
#Repository
#RepositoryRestResource
#Transactional(readOnly = true)
public interface ARepository extends PagingAndSortingRepository<A, Long> {
Page<A> findByNameContaining(String namePart, Pageable pageable);
#Query(
value = "SELECT a.name,\n" +
"(SELECT CAST(count(ac.id) AS int) FROM COMMENTS ac WHERE ac.a_id = a.id),\n" +
"(SELECT listagg(asf.id) FROM SIMPLE_FILES asf WHERE asf.a_id = a.id)\n" +
"FROM AS a\n" +
"WHERE a.id = :id",
nativeQuery = true
)
Optional<ACustomPage42DTO> getByIdProjectedForScreen42(Long id);
}
I have also tried to use JPQL, but there I had problem with fetch join (as I am not familiar with JPQL). My last evaluation query was something like this:
#Query("SELECT new sk.qpp.qqq.documents.projections.ACustomPage42DTO(" +
"a " +
"(SELECT CAST(count(ac) AS int) FROM COMMENTS ac WHERE ac.a = a)" +
")\n" +
"FROM A a\n" +
"LEFT JOIN FETCH a.simpleFiles\n" +
"WHERE a.id = :id"
)
I would like to get some general advice about what approach is best to implement custom and complex query to be returned in DTO (ideally with some specific links to actions when needed).
PS: Implementing interface and returning simple (primitive) data works. Also using JPQL to create custom DAO instance works (with simple types and with single instance of type A for example). Method for using given query method does appear in search methods of given entity endpoint. I would like to have something more reasonable, so I would like to have projection as defined in spring data rest project.
I have my DTO object fully under my control. I prefer it to use #Value or #Data annotation from project lombok, but it is not a need. I have tried also these versions of DTO definition (using interface works for simple data and similarly class works for simple data).
interface ACustomPage42DTO {
String getName();
long getCommentsCount();
Object getAsdf();
}
Or using equivalent class with some bonus, like custom toString() method possible, or some custom getter for computed data:
#Value //lombok thing, imutable "POJO"
public class ACustomPage42DTO {
String name;
long commentsCount;
Set<SimpleFile> simpleFiles;
public ACustomPage42DTO(A a, long count) {
// constructor used by JPQL, if it works
name = a.getName();
this.commentsCount = count;
this.simpleFiles = a.getSimpleFiles(); // should be already fetched, due to fetch join in JPQL
}
}
Both working approaches can be called using "search" url, instead of projection. I see my method getByIdProjectedForScreen42 on url http://localhost:9091/api/a/search listing. I would like to use it like (I think that is the "right" way) http://localhost:8080/api/a?projection=ACustomPage42DTOProjection .
Question is quite broad and touches couple of aspects:
custom JPA repository method using #Query
selecting results in your #Query
mapping #Query results to an interface
exposing new repository method through #RepositoryRestResource
TLDR: wrote an example of what is talked about with couple of basic tests https://github.com/ivarprudnikov/test-spring-jpa-repository-query-exposed-through-http
custom JPA repository method using #Query
As you have mentioned it is quite straightforward, just annotate a method with #Query and make sure your return type corresponds to what is being returned from the query, eg:
public interface FooRepository extends JpaRepository<FooEntity, Long> {
#Query(nativeQuery = true, value = "select f from foo f where f.name = :myParam")
Optional<FooEntity> getInSomeAnotherWay(String myParam);
}
selecting results in your #Query
You have given an example already but I'll simplify to make it easier and shorter.
Given entities FooEntity.java and BarEntity.java:
#Entity
#Table(name = "foo")
public class FooEntity {
#Id
#Column(name = "id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy = "foo")
private Set<BarEntity> bars = new HashSet<>();
// getter setters excluded for brevity
}
#Entity
#Table(name = "bar")
public class BarEntity {
#Id
#Column(name = "id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#ManyToOne(targetEntity = FooEntity.class)
#JoinColumn(name = "foo_id", nullable = false, foreignKey = #ForeignKey(name = "fk_bar_foo"))
private FooEntity foo;
// getter setters excluded for brevity
}
We want now to return custom result set which contains FooEntity.name and count of FooEntity.bars:
SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id
+-----------------+----------+
| name | barCount |
+-----------------+----------+
| Jonny tables | 1 |
+-----------------+----------+
mapping #Query results to an interface
To map above result set we need an interface where getters nicely reflect what is being selected:
public interface ProjectedFooResult {
String getName();
Long getBarCount();
}
Now we can rewrite our repository method to:
#Query(nativeQuery = true,
value = "SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id")
Optional<ProjectedFooResult> getByIdToProjected(Long id);
exposing new repository method through #RepositoryRestResource
I am not very familiar with this but after adding org.springframework.data:spring-data-rest-hal-browser dependency I got this nice interface that exposed available methods after repository was annotated with #RepositoryRestResource. For a given repository which contains above mentioned details:
#RepositoryRestResource(path = "foo")
public interface FooRepository extends JpaRepository<FooEntity, Long> {
#Query(nativeQuery = true, value = "SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id")
Optional<ProjectedFooResult> getByIdToProjected(Long id);
}
the method will be exposed through http://localhost:8080/foo/search/getByIdToProjected?id=1 when running locally.
As mentioned above the reference implementation is on Github https://github.com/ivarprudnikov/test-spring-jpa-repository-query-exposed-through-http
Additional helpful documentation for 'Custom Implementations for Spring Data Repositories'

Eager fetch an specific lazy property for a particular object

I have the following model:
#Table(name = "foo")
public class Foo {
#ManyToOne(fetch = FetchType.LAZY)
private Bar bar;
}
Using Entity Framework in .NET in a similar situation, I could eagerly bring the Bar property with something like:
context.Foo.Include(f => f.bar).First()
Is there anything equivalent with Hibernate?
My situation is that I'm saving an object with a lazy property into a session in my server. Then when I retrieve the session properties, I cannot access the lazy property for the Hibernate Session is already gone. I cannot put this property as EAGER because it is inherited from a #MappedSuperclass used by a lot of other classes.
Thanks for any help.
JPA EntityGraph:
#Entity
#Table(name = "foo")
#NamedEntityGraph(name = "foo.bar",
attributeNodes = #NamedAttributeNode("bar")
)
public class Foo {
#ManyToOne(fetch = FetchType.LAZY)
private Bar bar;
}
Foo foo = entityManager.find(
Foo.class,
id,
Collections.singletonMap(
"javax.persistence.fetchgraph",
entityManager.getEntityGraph("foo.bar")
)
);
You can see another example and more detail explanation there.
Hibernate profiles:
#Entity
#Table(name = "foo")
#FetchProfile(
name = "foo.bar",
fetchOverrides = {
#FetchProfile.FetchOverride(
entity = Foo.class,
association = "bar",
mode = FetchMode.JOIN
)
}
)
public class Foo {
#ManyToOne(fetch = FetchType.LAZY)
private Bar bar;
}
session.enableFetchProfile("foo.bar");
Foo foo = session.byId(Foo.class).load(id);
You can use a JPQL query with FETCH JOIN:
List<Foo> l = em.createQuery(
"SELECT f FROM Foo f JOIN FETCH f.bar", Foo.class)
.getResultList();
In this way all Foo class instances will be loaded with all Bar instances already fetched. You can tailor the query to fit your needs.

Mapping NativeQuery results when using inner join

I have the follow situation:
#Entity
#XmlRootElement
#Table(name = "TB_A")
public class A implements Serializable {
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CD_B")
private B b;
}
#Entity
#XmlRootElement
#Table(name = "TB_B")
public class B implements Serializable {
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "CD_B")
#JsonIgnore
private Set<C> c = new HashSet<>();
}
#Entity
#XmlRootElement
#Table(name = "TB_C")
public class C implements Serializable {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CD_B")
private B b;
}
I need to run the follow code:
String sql = "SELECT * FROM TB_A a "
+ "INNER JOIN TB_B b ON ... "
+ "LEFT JOIN TB_C c ON ... ";
Query query = em.createNativeQuery(sql, A.class);
List<A> AList = query.getResultList();
for(A a : AList) {
List<c> CList = a.getB().getC();
}
Analising the executed queries, I notice that JPS is running a SELECT each time I access elements B and C.
How can I correctly map this nativeQuery to use the lazyLoad?
Obs: I MUST use NativeQuery, because in my WHERE clause I need to use an especific function from Oracle. So JPQL is not an option for me (If I could use it, my life would be much more easy!).
Short answer: you can't.
What this line will do:
Query query = em.createNativeQuery(sql, A.class);
Is execute your SQL, then throw away anything not related to A.class.
Then your for loop:
for(A a : AList) {
List<c> CList = a.getB().getC();
}
Fetches everything again.
Here you use JPA again, which doesn't know anything about the query you executed before.
In some cases, you can try to map to C.class instead.
Then you'll be able to avoid the loop entirely.
If in your case you need both A.class and C.class, you can look into ResultSetMapping instead: http://www.java2s.com/Tutorial/Java/0355__JPA/SqlResultSetMappingForHierarchicalEntity.htm

JPA/Hibernate: bidirectional OneToMany/ManyToOne relation only works unidirectional

I'm currently experiencing problems with my OneToMany/ManyToOne-Mapping. The mapping looks like this:
public class A implements Serializable {
#EmbeddedId
private AId id;
// Other stuff...
}
#Embeddable
public class AId implements Serializable {
#ManyToOne
#JoinColumn(name = "B_ID", nullable = false)
private B b;
// Other stuff...
}
public class B implements Serializable {
#OneToMany(mappedBy = "id.b")
private List<A> as;
// Other stuff...
}
If I try to access object B by using object A everything works just fine, but the inverse direction doesn't work at all. The relationship is always null.
A objectA = findAById(id);
B objectB = objectA.getB(); // OK
// But... for example
objectB.getAs(); // returns null
I wrote a small query to get all the As for an object B using its primary key:
SELECT as FROM B b, IN(b.as) as WHERE b.id = :id
This works perfectly, I get the expected result.
I checked what is persisted in the DB, too, and it's all right. Has anybody a clue why that relationship only works in one direction?
Regards,
Alex
that's because by default #onetomany has lazy fetch. You can fix that using this
fetch = FetchType.EAGER
public class B implements Serializable {
#OneToMany(mappedBy = "id.b", fetch = FetchType.EAGER)
private List<A> as;
// Other stuff...
}

Categories