Same #NamedQueries in two different entities gives warning - java

I've same #NamedQueries in two entities as like below:
#Table(name = "STUDENT_TABLE")
#NamedQueries({
#NamedQuery(name = "getStudentById", query = "SELECT s FROM Student s where s.stdId=:stdId"),
#NamedQuery(name = "getStudentByName", query = "SELECT s FROM Student s where s.fName=:fName and s.lName =:lName")
})
#Table(name = "MARKS_TABLE")
#NamedQueries({
#NamedQuery(name = "getStudentById", query = "SELECT s FROM Student s where s.stdId=:stdId"),
#NamedQuery(name = "getStudentByName", query = "SELECT s FROM Student s where s.fName=:fName and s.lName =:lName")
})
While I'm working on the above, I'm getting a warning like below:
openjpa.MetaData: Warn: Ignoring duplicate query "getStudentById" in "class Student". A query with the same name been already declared in "class Marks".
openjpa.MetaData: Warn: Ignoring duplicate query "getStudentByName" in "class Student". A query with the same name been already declared in "class Marks".
What is the reason and how can we get rid of this warning?

The scope of #NamedQuery is the entire persistence unit.
It does not matter that they are defined on different entities.
Most likely when you define a duplicate name, one of them will be overriden during building of the persistence unit.
Good practice is to prefix your named queries with the entity names:
#NamedQuery(name = "Student.getStudentById"..
#NamedQuery(name = "Marks.getStudentById"...

Related

What is wrong with my native query in jpa?

I'm sending a very simple query to the database, but I'm getting an error. It feels like I'm missing something very simple. I guess it wouldn't allow me to create it because the word order is a keyword on the h2 db, so I put it in quotation marks within the table annotation.
#Query(value = "select * from `ORDER` o where o.basket_id= :basketId ", nativeQuery = true)
Optional<Order> getOrderByBasketId(Long basketId);
#Entity
#Getter
#Setter
#Table(name = "`ORDER`")
public class Order extends BaseExtendedModel{
private BigDecimal price;
#Enumerated(EnumType.STRING)
private OrderStatus orderStatus;
#OneToOne
private Customer customer;
#OneToOne(cascade = CascadeType.MERGE)
private Basket basket;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "order")
private OrderAddress orderAddress;
}
{
"errorMessage": "could not prepare statement; SQL [select * from `ORDER` o where o.basket_id= ? ]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement"
}
The problem is easier to identidy when you have a look at the logs. You'll see an entry like this:
org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "ORDER" not found; SQL statement:
So let's see what SQL statements are executed. So we add the following to application.properties
spring.jpa.show-sql=true
Assuming you let spring boot create your tables, you will see the following:
Hibernate: drop table if exists "order" CASCADE
Hibernate: create table "order" ...
And when we hit the repository method we see
select * from `ORDER` o where o.name= ? [42102-200]
So why did it create the table in lowercase, even though we specified #Table(name = "`ORDER`")?
The default for spring.jpa.hibernate.naming.physical-strategy is org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy, which
replaces dots with underscores
changes CamelCase to snake_case
lower-cases table names.
But we want it to take the names we use in #Table. That works when setting the property to spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl.
Yor native query will need the matching casing though.
You need to use index parameters
#Query(value = "select * from `ORDER` o where o.basket_id= ?1", nativeQuery = true)
Optional<Order> getOrderByBasketId(Long basketId);
or named parameters
#Query(value = "select * from `ORDER` o where o.basket_id= :basketId", nativeQuery = true)
Optional<Order> getOrderByBasketId(#Param("basketId") Long basketId);

Hibernate generates cross join instead of left outer join for optional relation

I modeled a database relationship in JPA like this:
#Entity
#Data
#NoArgsConstructor(access = AccessLevel.PROTECTED) // Required for JPA
#AllArgsConstructor
public class OuterEntity {
#Id
private String outerId;
#JoinColumn(nullable = true)
#ManyToOne(fetch = FetchType.LAZY, optional = true)
private InnerEntity inner;
}
#Entity
#Data
#NoArgsConstructor(access = AccessLevel.PROTECTED) // Required for JPA
#AllArgsConstructor
public class InnerEntity {
#Id
private String innerId;
#Column
private boolean deleted;
}
And persisted some test data as follows:
InnerEntity inner = new InnerEntity("inner", false);
OuterEntity outerWithInner = new OuterEntity("outerWithInner", inner);
OuterEntity outerWithoutInner = new OuterEntity("outerWithoutInner", null);
em.persist(inner);
em.persist(outerWithInner);
em.persist(outerWithoutInner);
Performing a very simple query using the criteria API successfully gives two results:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<OuterEntity> query = cb.createQuery(OuterEntity.class);
query.from(OuterEntity.class);
System.out.println(em.createQuery(query).getResultList());
// [OuterEntity(outerId=outerWithInner, inner=InnerEntity(innerId=inner, deleted=false)), OuterEntity(outerId=outerWithoutInner, inner=null)]
But once I add a filter on the inner entity like this:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<OuterEntity> query = cb.createQuery(OuterEntity.class);
Root<OuterEntity> root = query.from(OuterEntity.class);
query.where(cb.or(cb.isNull(root.get("inner")), cb.isFalse(root.get("inner").get("deleted"))));
hibernate generates a cross join with where outerentit0_.innerId_innerId=innerentit1_.innerId in the resulting SQL in order to access the inner entity's table:
select
outerentit0_.outerId as outerid1_1_,
outerentit0_.inner_innerId as inner_in2_1_
from OuterEntity outerentit0_
cross join InnerEntity innerentit1_
where
outerentit0_.inner_innerId=innerentit1_.innerId
and (
outerentit0_.inner_innerId is null
or innerentit1_.deleted=0
)
This now only returns one entity:
[OuterEntity(outerId=outerWithInner, inner=InnerEntity(innerId=inner, deleted=false))]
Which is not the result I desire. I expected there to still be two results.
I believe this happens because the filter outerentit0_.inner_innerId=innerentit1_.innerId removes the outer entity that has null for its inner, because using = on nulls always evaluates to false.
Note that I explicitly set the join column to nullable and the many-to-one relationship to optional in the JPA-annotations, so I expected JPA to properly handle the null-case instead.
If I add either root.join("inner", JoinType.LEFT); or root.join("inner", JoinType.LEFT);, I get the correct result because the generated query is using a left outer join:
select
outerentit0_.outerId as outerid1_1_,
outerentit0_.inner_innerId as inner_in2_1_
from OuterEntity outerentit0_
left outer join InnerEntity innerentit1_
on outerentit0_.inner_innerId=innerentit1_.innerId
where
outerentit0_.inner_innerId is null
or innerentit1_.deleted=0
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<OuterEntity> query = cb.createQuery(OuterEntity.class);
Root<OuterEntity> root = query.from(OuterEntity.class);
root.join("inner", JoinType.LEFT);
cb.isFalse(root.get("inner").get("deleted"))));
List<OuterEntity> result = em.createQuery(query).getResultList();
System.out.println(result);
// [OuterEntity(outerId=outerWithInner, inner=InnerEntity(innerId=inner, deleted=false)), OuterEntity(outerId=outerWithoutInner, inner=null)]
Which is a workable solution I suppose, but I am very confused why hibernate would emit a cross join, which is subtly wrong for my expectations, the worst kind of wrong. What did I do to cause this behaviour? Am I required to always explicitly perform a left outer join when dealing with nullable relations like this?

JPA crud repository query

Im trying to learn Spring and got problem with JPA repository queries
I have 2 classes in bidirectional relationships to each other:
public class MovieGenre {
// other fields
#ManyToMany(mappedBy = "genres")
#JsonBackReference
private Set<Movie> movies = new HashSet<>();
// ...
}
and
public class Movie {
// id and other fields
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "movie_movie_genre",
joinColumns = #JoinColumn(name = "movie_id"),
inverseJoinColumns = #JoinColumn(name = "movie_genre_id"))
#JsonManagedReference
private Set<MovieGenre> genres = new HashSet<>();
I would like to create controller to be able to get json with all the movies that are in one of the genres.
I was trying to get jpa query doing this for me.
public interface MovieRepository extends CrudRepository<Movie, Long> {
Stream<Movie> getMoviesByGenresIsLike(String genreName);
}
This dosn’t work
To give Idead what Im trying to achive this is normal sql query (and it works in h2 console)
SELECT * FROM MOVIE m
INNER JOIN movie_movie_genre mmg ON m.movie_id = mmg.movie_id
INNER JOIN movie_genre mg ON mmg.movie_genre_id = mg.genre_id
WHERE genre_name = 'action';
I was trying to write custom query like that
#Query(value = "SELECT * FROM MOVIE m \n" +
"INNER JOIN movie_movie_genre mmg ON m.movie_id = mmg.movie_id\n" +
"INNER JOIN movie_genre mg ON mmg.movie_genre_id = mg.genre_id\n" +
"WHERE genre_name = ?1;", nativeQuery = true)
Optional<Movie> getMoviesByGenres(#Param("name") String name);
This all leads to
2019-10-25 17:07:42.405 ERROR 831 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]
: Servlet.service() for servlet [dispatcherServlet] in context with path []
threw exception [Request processing failed;
nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.QueryException:
JPA-style positional param was not an integral ordinal;
nested exception is java.lang.IllegalArgumentException: org.hibernate.QueryException:
JPA-style positional param was not an integral ordinal] with root cause
Is there any way I can get list of movies by genre?
How to write correct custom query if jpa query is not possible?
EDIT:
-- I changed query to "SELECT m FROM Movie m INNER JOIN m.genres g WHERE g.genreName = ?1" (as suggested in answers below)
-- Another problem was lack of #Transactional annotation in controller.
Try switching from using native query to jpql notion:
"SELECT m FROM Movie m INNER JOIN m.genres g WHERE g.genreName = ?1"
Or you can create a MovieGenreRepository and nave a method like:
Stream<MovieGenre> findByGenreNameLike(String genreName);
First of all you must map your entities well and if the names of the fields are exactly the same as your SQL query then it would be something like this:
#Repository
public interface MovieRepository extends CrudRepository<Movie, Long> {
#Query(value = "SELECT m FROM Movie m INNER JOIN m.genres g ON m.movie_id = g.movie_id WHERE g.genreName = :name")
Movie getMoviesByGenres(#Param("name") String name);
}
Don't forget to put the #Repository annotation, since we must always follow the hibernate standard.

How to get jpql of named query?

Let say there is the below definition:
#Entity
#NamedQueries({
#NamedQuery(name = "name1", query = "SELECT n FROM SomeEntity n where n.name= :name"),
#NamedQuery(name = "name2", query = "SELECT n FROM SomeEntity n where n.name <> :name")
})
public class SomeEntity ......
How to programmatically get a definition of the named query ?
For example the bellow code snippet should print a definition of name1 query, I don't know how to get this definition in code:
String queryString = ????????.getSqlOfNamedQuery("name1");
// the below line should print: SELECT n FROM SomeEntity n where n.name= :name
System.out.println( queryString );
You can use the unwrap method and provide it with a vendor specific class. I saw this in an old DZone article. I've tested the EclipseLink version and it works for me:
Query query = entityManager.createQuery("....");
For EclipseLink:
String sqlString = query.unwrap(JpaQuery.class).getDatabaseQuery().getSQLString();
For Hibernate:
String sqlString = query.unwrap(org.hibernate.Query.class).getQueryString();
You could also check your database logs (i.e. /var/log/postgres/postgres.log) but you might have other requirements that necessitate a programmatic approach.

CRudRepository native query could not return Result Set

I have a simple test query inside a CrudRepository interface that should return a List of entities.
public interface TestRepository extends CrudRepository<Test, TestId> {
#Query(value = "SELECT p FROM test p ", nativeQuery = true)
public List<Test> getTests();
}
When I test this I get the exception:
org.springframework.dao.InvalidDataAccessResourceUsageException: could
not extract ResultSet
If I don't use native query it works, but I want to use native query because I want to extend the select.
In order to make your query work :-
#Query(value = "SELECT * FROM TEST ", nativeQuery = true)
public List<Test> getTests();
The reason is simply because you are writing native query."SELECT p FROM test p" is not a native query
2 problems
in native SQL use native SQL :)
#Query(value = "SELECT p.* FROM test p ", nativeQuery = true)
your native Query returns an Object[] or a List of Object[].
You can change that, if you provide additional mapping information to the EntityManager.
By doing this you can tell the EntityManager to map the result into managed entities, scalar values of specific types or POJOs.
The simplest way to map the result of a native query into a managed entity is to select all properties of the entity and provide its as a parameter to the createNativeQuery method.
(sorry for using other examples)
Query q = em.createNativeQuery("SELECT a.id, a.version, a.firstname, a.lastname FROM Author a", Author.class);
List<Author> authors = q.getResultList();
All other mappings, like the following one which maps the query result into a POJO, need to be defined as SQLResultSetMappings.
#SqlResultSetMapping(
name = "AuthorValueMapping",
classes = #ConstructorResult(
targetClass = AuthorValue.class,
columns = {
#ColumnResult(name = "id", type = Long.class),
#ColumnResult(name = "firstname"),
#ColumnResult(name = "lastname"),
#ColumnResult(name = "numBooks", type = Long.class)}))
To use this mapping, you need to provide the name of the mapping as a parameter to the createNativeQuery method.
Query q = em.createNativeQuery("SELECT a.id, a.firstname, a.lastname, count(b.id) as numBooks FROM Author a JOIN BookAuthor ba on a.id = ba.authorid JOIN Book b ON b.id = ba.bookid GROUP BY a.id", "AuthorValueMapping");
List<AuthorValue> authors = q.getResultList();
#Query(value = "SELECT * FROM test p ", nativeQuery = true)

Categories