"Could not prepare statement" OneToMany - spring

Here is what I'm trying to achieve :
Write a web service that gives the details of a repository: id, name, user name and
list of commits.
💡 It needs to be returned as a JSON format, for example :
{
"repository_id": "1",
"repository_name": "My repo",
"owner": "Noah",
"commits": [
"First commit",
"Second commit",
"Third commit"
]
}
You will find the database structure below :
Here is my CrudRepository with the query I am trying to build:
public interface RepositoriesDB extends CrudRepository<Repository, String> {
#Query(value = "SELECT r.repositoryId, r.repositoryName, r.owner.userName, r.commits FROM Repository r WHERE r.repositoryId = :repoId")
List<Object[]> getRepo(#Param("repoId") long repoId);
}
My User class :
#Entity
#NoArgsConstructor
#Data
public class User {
#NotNull
#Id
private String userLogin;
#NotBlank
#NotNull
private String userName;
#OneToMany(mappedBy = "owner", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnore
private List<Repository> repositories;
}
My Repository class :
#Entity
#NoArgsConstructor
#Data
public class Repository {
#NotNull
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long repositoryId;
#NotNull
#NotBlank
#Size(min = 3, max = 25)
private String repositoryName;
#OneToMany(mappedBy = "repository", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnore
private List<Commit> commits;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "owner_login", nullable = false)
private User owner;
}
My Commit class :
#Entity
#NoArgsConstructor
#Data
public class Commit {
#NotNull
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long commitId;
#NotBlank
#NotNull
private LocalDateTime date = LocalDateTime.now();
#NotNull
#NotBlank
#Size(min = 1, max = 255)
private String message;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "repository_id", nullable = false)
private Repository repository;
}
And finally, here is the stacktrace :
There was an unexpected error (type=Internal Server Error, status=500).
could not prepare statement; SQL [select repository0_.repository_id as col_0_0_, repository0_.repository_name as col_1_0_, user1_.user_name as col_2_0_, . as col_3_0_, commits2_.commit_id as commit_i1_0_, commits2_.date as date2_0_, commits2_.message as message3_0_, commits2_.repository_id as reposito4_0_ from repository repository0_ cross join user user1_ inner join commit commits2_ on repository0_.repository_id=commits2_.repository_id where repository0_.owner_login=user1_.user_login and repository0_.repository_id=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [select repository0_.repository_id as col_0_0_, repository0_.repository_name as col_1_0_, user1_.user_name as col_2_0_, . as col_3_0_, commits2_.commit_id as commit_i1_0_, commits2_.date as date2_0_, commits2_.message as message3_0_, commits2_.repository_id as reposito4_0_ from repository repository0_ cross join user user1_ inner join commit commits2_ on repository0_.repository_id=commits2_.repository_id where repository0_.owner_login=user1_.user_login and repository0_.repository_id=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:259)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551)
[...]
Caused by: org.hibernate.exception.SQLGrammarException: could not prepare statement
at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:63)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
[...]
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "SELECT REPOSITORY0_.REPOSITORY_ID AS COL_0_0_, REPOSITORY0_.REPOSITORY_NAME AS COL_1_0_, USER1_.USER_NAME AS COL_2_0_, .[*] AS COL_3_0_, COMMITS2_.COMMIT_ID AS COMMIT_I1_0_, COMMITS2_.DATE AS DATE2_0_, COMMITS2_.MESSAGE AS MESSAGE3_0_, COMMITS2_.REPOSITORY_ID AS REPOSITO4_0_ FROM REPOSITORY REPOSITORY0_ CROSS JOIN USER USER1_ INNER JOIN COMMIT COMMITS2_ ON REPOSITORY0_.REPOSITORY_ID=COMMITS2_.REPOSITORY_ID WHERE REPOSITORY0_.OWNER_LOGIN=USER1_.USER_LOGIN AND REPOSITORY0_.REPOSITORY_ID=?"; expected "*, NOT, EXISTS, INTERSECTS, UNIQUE"; SQL statement:
select repository0_.repository_id as col_0_0_, repository0_.repository_name as col_1_0_, user1_.user_name as col_2_0_, . as col_3_0_, commits2_.commit_id as commit_i1_0_, commits2_.date as date2_0_, commits2_.message as message3_0_, commits2_.repository_id as reposito4_0_ from repository repository0_ cross join user user1_ inner join commit commits2_ on repository0_.repository_id=commits2_.repository_id where repository0_.owner_login=user1_.user_login and repository0_.repository_id=? [42001-200]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:453)
at org.h2.message.DbException.getJdbcSQLException(DbException.java:429)
at org.h2.message.DbException.getSyntaxError(DbException.java:243)
at org.h2.command.Parser.getSyntaxError(Parser.java:1053)
[...]
I believe the problem comes from the fact that I'm trying to select a OneToMany relation, since I don't have this issue when selecting the owner because I have an #JsonIgnore annotation and infinite loops...
Except, in this case, I NEED to select the list of transaction :(
Thanks in advance for your help !

The error in your sql query is the r.commits part. It is a list of values and sql column accept only a single (or scalar) value type like number, varchar etc
Since the relation between Repository and Commit entities isOne-To-Many association so r.commits is a list of values so Hibernate fails to prepare the sql statement.
You can remove the r.commits part from your sql query and it will works.
If you would like to get the list of commits for your repository, you can implement a specefic method for that.
Something like
public interface CommitRepository extends JpaRepository<Commit, Long> {
List<Commit> findAllByRepository(Repository repository);
}
you can pass to this method the repository object that you want to get all the associated commits.
Also, since the Repository.repositoryId is Long data type so you have to change the RepositoriesDB defenition to
public interface RepositoriesDB extends JpaRepository<Repository, Long>

Related

ERROR: syntax error at or near "." - JPA Pageable

repository:
#Repository
public interface PostRepository extends PagingAndSortingRepository<Post, Long> {
#Query(value = "SELECT p.postComments FROM Post p WHERE p.webId = ?1")
Page<PostComment> findCommentsByWebId(String webid, Pageable pageable);
}
Post entity:
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "web_id")
private String webId;
#Column(nullable = false, name = "title")
private String title;
#Column(nullable = false, name = "description")
private String description;
#Column(nullable = false, name = "mature")
private boolean mature;
#OneToOne(mappedBy = "post")
private Cover cover;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#OneToMany(mappedBy = "post")
private List<PostView> postViews;
#ManyToMany
#JoinTable(name = "post_tag",
joinColumns = #JoinColumn(name = "post_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id"))
private List<Tag> tags;
#OneToMany(mappedBy = "post")
private List<PostDownvote> postDownvotes;
#OneToMany(mappedBy = "post")
private List<PostUpvote> postUpvotes;
#OneToMany(mappedBy = "post")
private List<PostComment> postComments;
#Column(name = "created_at")
private Timestamp createdAt;
#Column(name = "updated_at")
private Timestamp updatedAt;
}
The problem: When returning plain List<PostComment> from the query method everything works fine. But if I change it to Page<PostComment> (I need total elements count), I get the following error:
2022-08-03 22:29:41.399 ERROR 9192 --- [nio-8080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: syntax error at or near "."
Position: 14
Hibernate: select tags0_.post_id as post_id1_6_0_, tags0_.tag_id as tag_id2_6_0_, tag1_.id as id1_10_1_, tag1_.name as name2_10_1_ from post_tag tags0_ inner join tag tag1_ on tags0_.tag_id=tag1_.id where tags0_.post_id=?
org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
It is very difficult to debug this. Does anyone have any clue on what is wrong?
I need BOTH paging and total amount of elements.
Basically you are not able to fetch the part of the inner collection. But you could reach it from the another side of the bi-directional relationship
#Repository
public interface PostCommentRepository extends PagingAndSortingRepository<PostComment, Long> {
#Query(value = "SELECT pc FROM PostComment pc WHERE pc.post.webId = ?1")
Page<PostComment> findCommentsByWebId(String webid, Pageable pageable);
// or better using Spring Data naming conventions just
Page<PostComment> findAllByPostWebId(String webid, Pageable pageable);
}
If you only need a total count you should avoid querying list of entities which could be very memory intensive.
So in your PostCommentRepository try the following:
long countAllByPost_WebId(String webId);

How to retrieve only a specific field from child entity on #OneToOne relationship, not all fields?

When I use jpa's #OneToOne annotation, I want to get the userName field from the table, not all fields. What should I do instead?
#Setter
#Getter
#Entity
public class Menu implements Serializable {
private static final long serialVersionUID = 4462798713783196961L;
/**
* id
*/
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#OneToOne
#JoinColumn(name = "createUserId",referencedColumnName = "userId")
private User createUser;
#Column(nullable = false)
private LocalDateTime createTime;
}
What do I need to do, can I get the userName field in the User object, but not all of it? Thank you in advance.
You can create a POJO with required fields. e.g. You only want id from Menu and userName from User:
public class CustomMenu {
private Long menuId;
private String userName;
public CustomMenu(Long menuId, String userName) {
this.menuId = menuId;
this.userName = userName;
}
// getters, setters
}
Then you can write a query with hql using the constructor in the CustomMenu with parameters new com.yourpackage.CustomMenu(m.id, m.createUser.userName) and join User entity (join m.createUser) :
TypedQuery<CustomMenu> query = entityManager.createQuery("select new com.yourpackage.CustomMenu(m.id, m.createUser.userName)"
+ "from com.yourpackage.Menu m join m.createUser", CustomMenu.class);
List<CustomMenu> menus = query.getResultList();
This generates one sql query with inner join fetching only required fields :
select menu0_.id as col_0_0_, user1_.user_name as col_1_0_ from menu menu0_ inner join user user1_ on menu0_.create_user_id=user1_.user_id

Problem Retrieving Nextval in JPA Application

I need have a need to return nextVal at various times in my application. I have an entity class like the following:
#Entity
#Table(name = "TBL_CACL")
public class Cacl {
#Id
#SequenceGenerator(name = "caclGenerator", sequenceName = "caclSeq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.IDENTITY, generator = "calcGenerator")
#Column(name = "ID")
private Integer id;
// more stuff below....
, and I added the following in my repository interface:
#Repository
public interface CaclRepository extends JpaRepository<Cacl, Integer> {
#Query(value = "SELECT caclSeq.nextval FROM Cacl", nativeQuery = true)
Long getNextSeriesId();
However when I attempt to read it like this:
long nextval = caclRepository.getNextSeriesId() + 1;
, I get this exception:
(can't show entire stack trace)
org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
Caused by: org.postgresql.util.PSQLException: ERROR: relation "cacl" does not exist
Its puzzling to me that I am getting error "cacl does not exist" because this application has been up and working for some time. All that I have done is add the #SequenceGenerator, updated the #GeneratorValue to link to the #SequenceGenerator annotation, and create the new query. I would be grateful for any ideas as to what I am doing wrong. thanks
My answer is based on simplifying my approach some. Now I am simply using the default sequences supplied by postgress. So for instance now I have:
#Entity
#Table(name = "TBL_CACL")
public class Cacl {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Integer id;
#Column(name="NAME")
private String name;
// more stuff below...
, then in the repository (after querying postgress to get default sequences) I have:
#Query(value = "select last_value from tbl_cacl_id_seq", nativeQuery = true)
public Integer getCurrentVal();
And then:
int nextval = caclRepository.getCurrentVal();
works fine.
you could try removing the native query
#Query(value = "SELECT nextval('caclSeq')", nativeQuery =
true)
Long getNextSeriesId();

cross join in SpringBoot 2.1.4.RELEASE app

I have a SpringBoot 2.1.4.RELEASE RESTful Web Service app., using Spring Initializer, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file. I am using an inMemoryDatabase : H2 Database Engine
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
I have these objects:
#Entity
#Table(name="t_menu_alert_notification")
public class MenuAlertNotification implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonProperty("id")
private Long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
#JoinColumn(name = "menu_alert_id")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="name")
#JsonIdentityReference(alwaysAsId=true)
protected MenuAlert menuAlert;
...
}
#Entity
#Table(name="t_menu_alert")
public class MenuAlert implements Serializable {
public MenuAlert() {
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "menu_id")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="name")
#JsonIdentityReference(alwaysAsId=true)
Menu menu;
..
}
#Entity
#Table(name = "t_menu")
#JsonPropertyOrder({ "id", "name", "address", "description" })
public class Menu implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonProperty("id")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
#JsonIgnore
private User user;
..
}
I have this method in the repository class that extends from a CrudRepository
#Transactional
#Modifying
#Query("update MenuAlertNotification n set n.read = :status where n.id in :notificationIdList and n.menuAlert.menu.user.id = :userId")
void changeMenuNotificationListReadStatus( #Param("notificationIdList") List<Long> notificationIdList,
#Param("userId") long userId,
#Param("status") boolean status);
}
But when I run this method I have this error:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [update t_menu_alert_notification cross join set is_read=? where (id in (?)) and user_id=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:279)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:253)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
Caused by: org.hibernate.exception.SQLGrammarException: could not prepare statement
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 51 more
Caused by: org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "UPDATE T_MENU_ALERT_NOTIFICATION CROSS[*] JOIN SET IS_READ=? WHERE (ID IN (?)) AND USER_ID=? "; expected "., AS, SET"; SQL statement:
update t_menu_alert_notification cross join set is_read=? where (id in (?)) and user_id=? [42001-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
at org.h2.message.DbException.getSyntaxError(DbException.java:217)
at org.h2.command.Parser.getSyntaxError(Parser.java:555)
at org.h2.command.Parser.read(Parser.java:3518)
at org.h2.command.Parser.parseUpdateSetClause(Parser.java:785)
at org.h2.command.Parser.parseUpdate(Parser.java:780)
at org.h2.command.Parser.parsePrepared(Parser.java:485)
at org.h2.command.Parser.parse(Parser.java:335)
at org.h2.command.Parser.parse(Parser.java:311)
at org.h2.command.Parser.prepareCommand(Parser.java:278)
at org.h2.engine.Session.prepareLocal(Session.java:611)
at org.h2.engine.Session.prepareCommand(Session.java:549)
at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1247)
at org.h2.jdbc.JdbcPreparedStatement.<init>(JdbcPreparedStatement.java:76)
at org.h2.jdbc.JdbcConnection.prepareStatement(JdbcConnection.java:304)
at com.zaxxer.hikari.pool.ProxyConnection.prepareStatement(ProxyConnection.java:311)
at com.zaxxer.hikari.pool.HikariProxyConnection.prepareStatement(HikariProxyConnection.java)
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$1.doPrepare(StatementPreparerImpl.java:87)
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:172)
... 73 more
According to the official documentation:
No joins, either implicit or explicit, can be specified in a bulk HQL
query.
So you need to find n.menuAlert first, then pass as parameter to update query or using native query.

unable to translate native sql query to jpa query

Hi I am new to jpql and using spring data jpa with postgres and I am not able to translate below query.
select
"user".table_1.id, "user".table_2.name,
"user".table_2.email
from
"user".table_1
left outer join
"user".table_2
on
"user".table_1.table2_id = "user".table_2.id
where
"user".table_1.parent_id=5
and below is my model classes
#entity
#table(name="table_1)
class Table1{
#id
#GeneratedValue
private Long id;
#OneToOne(mappedBy = "table_2")
private Table2 table_2;
#ManyToOne
#JoinColumn(name = "parent_id")
private Table1 parent_id;
#OneToMany(mappedBy = "account", fetch = FetchType.LAZY)
private List<Table1> childs;
}
#entity
#table(name="table_2)
class Table2
{
#id
private Long id;
private String emailId;
private String name;
#OneToOne
#JoinColumn(name = "table1_id")
private Table1 table1;
}
Since I am using DTO with spring data and I need help I am not able to solve this.
This is best I tried:
#query("select t1.id, t2.name,t2.email from Table1 t1 left outer join
t2.table_2 where t1.parent_id=?1")
public List<CustomDTO>findByParentId(Long parentId);
public class CustomDTO{
private Long table1Id;
private String name;
private String email;
}
I am not able to solve this error as I am getting the hibernate qwery as
select
table10_.id as col_0_0_,
table21_.name as col_1_0_,
table21_.email as col_2_0_
from
"user".table1 table0_
left outer join
"user".table2 table_21_
on table10_.id=table_21_.table_1 where
table0_.parent_id=?
Please help me to solve this error
If you need any help let me know.
Thanks :)
Your JPA query would look like (not tested though)
#Query("select t1.id as table1Id, t2.name as name ,t2.emailId as email from Table1 t1 join table_2 t2 where t1.parent_id= :parentId")
public List<CustomDTO>findByParentId(Long parentId){
public interface CustomDTO{
private Long table1Id;
private String name;
private String email;
}

Resources