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);
Related
I have searched all over stackoverflow.com but I have not found this discussion.
I know my problem, I was just wondering if JPA can handle this in any way without a lot of work...
What I have is an #Entity that I want to use with multiple SQL-Native-Queries.
#Entity
public class TestEntity {
#Id
private String ID;
private String ID1;
private String ID2;
private String ID3;
}
Names in the database are equal to Field names.
I then have two selects:
SELECT ID1, ID2 from DATABASE;
SELECT ID2 from DATABASE;
And two native queries for those selects:
#Query(value = "SELECT ID2 from DATABASE", nativeQuery = true)
List<TestEntity> testNativeQuery ();
And the Error I get:
[ERROR] 2021-11-30 10:37:20 [main] [SqlExceptionHelper] The column name ID1 is not valid.
How could I tell JPA that if it does not find the Column name that it should just replace it will
null
?
Would that be so difficult?
Some extra details:
I am using a Stored Procedure - which means I can not change the Query - The Stored Procedures are maintained by someone else.
Stored procedure (which works):
#Query(value = "STORED_PROC :a, :b", nativeQuery = true)
List<List<String>> execStoredProc(#Param("a") String a, #Param("b") String b);
Stored procedure that does not work, because I have more fields in the entity that the Procedure return, because I need those fields in other stored procedures:
#Query(value = "STORED_PROC :a, :b", nativeQuery = true)
List<TestEntity> execStoredProc(#Param("a") String a, #Param("b") String b);
When I remove those field the function works - but I need those fields in other part of my program - and I hoped I could use the same #Entity
Database I am using is MSSQL Database:
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
Simply add the missing columns as null:
#Query(value = "SELECT NULL AS ID, NULL AS ID1, ID2, NULL AS ID3 from DATABASE", nativeQuery = true)
List<TestEntity> testNativeQuery ();
Our in-house framework built with Java 11, Spring Boot, Hibernate 5 and QueryDSL does a lot of auto-generation of queries. I try to keep everything efficient and load associations only when needed.
When loading full entities, the programmer can declare a NamedEntityGraph to be used. Now there is one case where a query like this is generated:
select user.groups
from User user
where user.id = ?1
Where the Entities in question look like this:
#Entity
#NamedEntityGraph(name = User.ENTITY_GRAPH,
attributeNodes = {
#NamedAttributeNode(User.Fields.permissions),
#NamedAttributeNode(value = User.Fields.groups, subgraph = "user-groups-subgraph")
},
subgraphs = #NamedSubgraph(
name = "user-groups-subgraph",
attributeNodes = {
#NamedAttributeNode(Group.Fields.permissions)
}
))
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = Permission.class)
#CollectionTable(name = "USERS_PERMISSIONS", joinColumns = #JoinColumn(name = "uid"))
private Set<Permission> permissions = EnumSet.of(Permission.ROLE_USER);
#ManyToMany(fetch = LAZY)
private Set<Group> groups = new HashSet<>();
}
#Entity
#NamedEntityGraph(name = Group.ENTITY_GRAPH,
attributeNodes = {
#NamedAttributeNode(value = Group.Fields.permissions)
})
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = Permission.class)
#CollectionTable(
name = "GROUPS_PERMISSIONS",
joinColumns = #JoinColumn(name = "gid")
)
#NonNull
private Set<Permission> permissions = EnumSet.noneOf(Permission.class);
}
When selecting either User or Group directly, the generated query simply applies the provided NamedEntityGraphs. But for the above query the exception is:
org.hibernate.QueryException:
query specified join fetching, but the owner of the fetched association was not present in the select list
[FromElement{explicit,collection join,fetch join,fetch non-lazy properties,classAlias=user,role=foo.bar.User.permissions,tableName={none},tableAlias=permission3_,origin=null,columns={,className=null}}]
I first tried the User graph, but since we are fetching Groups, I tried the Group graph. Same Exception.
Problem is, there is no easy way to add a FETCH JOIN to the generated query, since I don't know which properties of the association should be joined in anyway. I would have to load the Entitygraph, walk it and any subgraph and generated the right join clauses.
Some more details on Query generation:
// QueryDsl 4.3.x Expressions, where propType=Group.class, entityPath=User, assocProperty=groups
final Path<?> expression = Expressions.path(propType, entityPath, assocProperty);
// user.id = ?1
final BooleanExpression predicate = Expressions.predicate(Ops.EQ, idPath, Expressions.constant(rootId));
// QuerydslJpaPredicateExecutor#createQuery from Spring Data JPA
final JPQLQuery<P> query = createQuery(predicate).select(expression).from(path);
// Add Fetch Graph
((AbstractJPAQuery<?, ?>) query).setHint(GraphSemantic.FETCH.getJpaHintName(), entityManager.getEntityGraph(fetchGraph));
EDIT:
I can reproduce this with a simple JPQL Query. It's very strange, if I try to make a typed query, it will select a List of Sets of Group and untyped just a List of Group.
Maybe there is something conceptually wrong - I'm selecting a Collection and I'm trying to apply a fetch join on it. But JPQL doesn't allow a SELECT from a subquery, so I'm not sure what to change..
// em is EntityManager
List gs = em
.createQuery("SELECT u.groups FROM User u WHERE u.id = ?1")
.setParameter(1, user.getId())
.setHint(GraphSemantic.FETCH.getJpaHintName(), em.getEntityGraph(Group.ENTITY_GRAPH))
.getResultList();
Same Exception:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
So the problem can be distilled down to a resolution problem of the Entit Graphs attributes:
select user.groups
from User user
where user.id = ?1
With the Entity Graph
EntityGraph<Group> eg = em.createEntityGraph(Group.class);
eg.addAttributeNodes(Group.Fields.permissions);
Gives an Exception that shows that Hibernate tries to fetch User.permissions instead of Group.permissions. This is the bug report.
And there is another bug regarding the use of #ElementCollection here.
I got OneToMany mapping
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#LazyCollection(LazyCollectionOption.EXTRA)
#OneToMany(mappedBy = "user")
#Where(clause = "is_del = 0")
private List<MyEntity> activities = Lists.newArrayList();
Access to this field will send bellow SQLs
Hibernate:
select
count(id)
from
activity_info
where
user_id =?
Hibernate:
select
...
from
activity_info activities0_
inner join
addr_info addrinfoen1_
on activities0_.addr_info_id=addrinfoen1_.id
where
(
activities0_.is_del = 0
)
and activities0_.user_id=?
Due to the EXTRA lazy, access to activities.size() will only send the first query, the #Where condition has been ignored.
Is this a bug or am I wrong about this usage ?
I have two entities in MySQL as below. The primary key of nnm_tran is a composite of id and source. The primary key of bargains is actually a foreign key link to the nnm_tran table
I'm trying to use JPA inheritance to represent these.
nnm_tran entity
#Entity
#Table(name = "nnm_tran")
#IdClass(CommonTransactionKey.class)
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "bargain_flag", discriminatorType = DiscriminatorType.CHAR)
#DiscriminatorValue("N")
public class CommonTransaction {
#Id
#Column(name = "id", nullable = false)
private String transactionId;
#Column(name = "plan_number", nullable = false)
private String planNumber;
#Column(name = "tran_date")
private LocalDateTime transactionDatetime;
#Column(name = "bargain_flag")
private String bargainFlag;
...
}
bargains entity
#Data
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "bargains")
#DiscriminatorValue("B")
#PrimaryKeyJoinColumns({ #PrimaryKeyJoinColumn(name = "nnm_tran_id", referencedColumnName = "id"), #PrimaryKeyJoinColumn(name = "nnm_tran_source", referencedColumnName = "source") })
public class Bargain extends CommonTransaction implements Serializable {
#Column(name = "unit_price")
private BigDecimal unitPrice;
#Column(name = "client_price")
private BigDecimal clientPrice;
...
}
I think so far this is all hooked up correctly. My problem comes when I attach a spring-data repository with a custom query.
Repository
public interface CommonTransactionRepository extends CrudRepository<CommonTransaction, CommonTransactionKey> {
#Query("select t from CommonTransaction t left join IoPlan p ON t.planNumber = p.planNumber "
+ "where (p.planNumber is NULL or p.planNumber = '') "
+ "and t.transactionDatetime between ?1 and ?2 "
+ "and t.cancelled = false")
public Iterable<CommonTransaction> findOrphanedTransactionsByTranDate(LocalDateTime fromDate, LocalDateTime toDate);
...
}
When this gets proxied and the method is executed it generates the SQL statement
SELECT DISTINCT nnm_tran.bargain_flag FROM nnm_tran t1 LEFT OUTER JOIN io_plan t0 ON (t1.plan_number = t0.plan_number) WHERE ((((t0.plan_number IS NULL) OR (t0.plan_number = ?)) AND (t1.tran_date BETWEEN ? AND ?)) AND (t1.CANCELLED = ?))
The issue with this is that the nnm_tran table is aliased to t1 but the discriminator column is referencing the full table name nnm_tran.bargain_flag The result is a lovely
UnitOfWork(17171249)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'nnm_tran.bargain_flag' in 'field list'
Question here is, am I doing something wrong or is this a bug in spring-data and/or eclipselink?
Versions: spring-data 1.7.2, Eclipselink 2.5.2, MySQL 5.6.28
Using #manish's sample app as a starting point I started layering back on the complexity that was missing and quickly stumbled across the thing causing the rogue SQL. It was down to the join I had performed in the JPQL
NOTE: If you've come here from the future then ignore the remainder of this answer and instead use #Chris's comment instead.
Most of the time I don't need to look at or even think about the IoPlan table that can be seen in the #Query
#Query("select t from CommonTransaction t left join IoPlan p ON t.planNumber = p.planNumber "
+ "where (p.planNumber is NULL or p.planNumber = '') "
+ "and t.transactionDatetime between ?1 and ?2 "
+ "and t.cancelled = false")
and so this table is not a part of the CommonTransaction entity as a field. Even the result of this query doesn't really care because it's looking only as a one off for CommonTransaction with no associated join in the IoPlan table.
When I added the join back in to the sample app from #manish it all broke in the same way my app has in EclipseLink, but broke in a different way for Hibernate. Hibernate requires a field for you to join with, which if you ask me defeats the purpose of writing the join in the #Query. In fact in Hibernate you have to define the join purely in JPA so you might as well then use dot notation to access it in the JPQL.
Anyway, going along with this idea I tried adding a dummy field to hold an IoPlan in my CommonTransaction entity and it almost worked. It defaulted some of the join logic but it was closer
SELECT DISTINCT t1.bargain_flag FROM nnm_tran t1 LEFT OUTER JOIN io_plan t0 ON ((t0.ID = t1.IOPLAN_ID) AND (t1.plan_number = t0.plan_number)) WHERE ((((t0.plan_number IS NULL) OR (t0.plan_number = ?)) AND (t1.tran_date BETWEEN ? AND ?)) AND (t1.CANCELLED = ?))
In this case t1.IOPLAN_ID and t0.ID don't exist. So I ended up defining the entire join in my CommonTransaction entity
#OneToOne
#JoinColumn(insertable = false, updatable = false, name = "plan_number", referencedColumnName = "plan_number")
private IoPlan ioPlan;
and voila, it started working. It's not pretty and now I have a redundant join condition
LEFT OUTER JOIN io_plan t1
ON ((t1.plan_number = t0.plan_number) AND (t0.plan_number = t1.plan_number))
but I can fix that. It's still annoying that I have to define a field for it whatsoever, I don't actually want or need it there, not to mention that the result from this query is returning CommonTransaction entities that have no IoPlan so the field will be permanently null.
Hibernate / Java newbie here, any help will be greatly appreciated!
So...... I have a table called ITEMS and a ITEM_OWNER_JOIN table joined by the
"itemKey" column and the "owners" column which is a Set of String values...
In Item.java I have:
#ForeignKey(name="FK_ITEM_OWNER_FK")
#ElementCollection(targetClass=java.lang.String.class, fetch = FetchType.Eager)
#JoinTable(name= "ITEM_OWNER_JOIN", joinColumns=#JoinColumn(name="itemKey"))
private Set<String> owners = new HashSet<String>();
and basically I'm trying to run a HQL querying for results where the owners match a
searchText param....
so I've tried:
Query q = session.createQuery("select distinct i.itemKey from Item i inner join"+
" i.owners o where o.owners like '"+searchText+"'");
and I am getting a org.hibernate.QueryException: cannot dereference scalar collection element: owners [select distinct w.workspaceKey from.....]
I've tried researching for that exception to no avail... :(
Thank you for your time!
Something as below
HQL
select i
from Item i
inner join i.owners io
where io like 'searchText';
Oracle Query
SELECT Distinct(i.itemKey)
FROM Item i, ITEM_OWNER_JOIN io
WHERE i.itemKey = io.itemKey and io.x like '%%';
where 'x' is column name.
Working example from my application
From entity:
#ElementCollection
#JoinTable(name = "rule_tagged_name", joinColumns = #JoinColumn(name = "re_rule", referencedColumnName = "id"))
private List<String> ruleTagNames;
DB Columns
RE_RULE NUMBER
RULE_TAG_NAMES
HQL
Select ru FROM Rule ru inner join ru.ruleTagNames rt_name WHERE rt_name in :tagNameList
Try using with IN operator as owners is multiple.
Query hqlQuery = session.createQuery("select distinct i.itemKey from Item i inner join"+
" i.owners o where o.owners in :ownersParam");
Then set parameter owners with the owner set value,
Set<String> ownerSet = new HashSet<String>();
ownerSet.add(searchText);
hqlQuery.setParameterList("ownersParam", ownerSet);
//then retrieve result