HsqlException: incompatible data types in combination - spring

Since I moved my integration tests to an in-memory HSQLDB (want to avoid having to provide a proper MySQL everytime and clearing the database after every test), I am getting this error message everytime the CrudRepository#findById() method is called
Caused by: org.hsqldb.HsqlException: incompatible data types in combination
at org.hsqldb.error.Error.error(Unknown Source) ~[hsqldb-2.5.1.jar:2.5.1]
at org.hsqldb.error.Error.error(Unknown Source) ~[hsqldb-2.5.1.jar:2.5.1]
at org.hsqldb.types.CharacterType.getAggregateType(Unknown Source) ~[hsqldb-2.5.1.jar:2.5.1]
at org.hsqldb.types.Type.getAggregateType(Unknown Source) ~[hsqldb-2.5.1.jar:2.5.1]
and that's the case for GET /ID, PATCH /ID and DELETE /ID calls to the REST API (spring data rest). The outer exception is
Caused by: java.sql.SQLSyntaxErrorException: incompatible data types in combination in statement [select trainingre0_.id as id1_15_0_, trainingre0_.matching_rule_id as matching3_15_0_, trainingre0_.tenant as tenant2_15_0_, matchingru1_.id as id1_2_1_, matchingru1_.mode as mode1_0_1_, matchingru1_.mode as mode1_3_1_, matchingru1_.property as property2_3_1_, matchingru1_.value as value3_3_1_, matchingru1_.ignore_case as ignore_c1_6_1_, matchingru1_.mode as mode2_6_1_, matchingru1_.property as property3_6_1_, matchingru1_.value as value4_6_1_, matchingru1_.clazz_ as clazz_1_, matchingru2_.composite_matching_rule_id as composit1_1_2_, matchingru3_.id as matching2_1_2_, matchingru3_.id as id1_2_3_, matchingru3_.mode as mode1_0_3_, matchingru3_.mode as mode1_3_3_, matchingru3_.property as property2_3_3_, matchingru3_.value as value3_3_3_, matchingru3_.ignore_case as ignore_c1_6_3_, matchingru3_.mode as mode2_6_3_, matchingru3_.property as property3_6_3_, matchingru3_.value as value4_6_3_, matchingru3_.clazz_ as clazz_3_ from training_request_subscription trainingre0_ left outer join ( select id, mode, cast(null as varchar(100)) as property, cast(null as int) as value, cast(null as boolean) as ignore_case, 1 as clazz_ from composite_matching_rule union all select id, mode, property, value, cast(null as boolean) as ignore_case, 2 as clazz_ from number_matching_rule union all select id, mode, property, value, ignore_case, 3 as clazz_ from string_matching_rule ) matchingru1_ on trainingre0_.matching_rule_id=matchingru1_.id left outer join composite_matching_rule_matching_rules matchingru2_ on matchingru1_.id=matchingru2_.composite_matching_rule_id left outer join ( select id, mode, cast(null as varchar(100)) as property, cast(null as int) as value, cast(null as boolean) as ignore_case, 1 as clazz_ from composite_matching_rule union all select id, mode, property, value, cast(null as boolean) as ignore_case, 2 as clazz_ from number_matching_rule union all select id, mode, property, value, ignore_case, 3 as clazz_ from string_matching_rule ) matchingru3_ on matchingru2_.matching_rules_id=matchingru3_.id where trainingre0_.id=?]
but let me show you the domain model which hopefully makes it clearer:
TrainingRequestSubscription (the entity I that creates the error when read):
#Entity
#Data
#EqualsAndHashCode(callSuper = false)
#AllArgsConstructor
#NoArgsConstructor
#Builder
#RequiredArgsConstructor
#ValidTrainingRequestSubscription
public class TrainingRequestSubscription extends AbstractBaseEntity implements TenantScoped {
#NotNull
#JsonProperty(access = Access.WRITE_ONLY)
private String tenant;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#NotNull
private #NonNull MatchingRule matchingRule;
}
MatchingRule
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#Getter
#Setter
#NoArgsConstructor
// #formatter:off
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = NumberMatchingRule.class, name = "number"),
#Type(value = StringMatchingRule.class, name = "string"),
#Type(value = CompositeMatchingRule.class, name = "composite")
})
//#formatter:on
public abstract class MatchingRule extends AbstractBaseEntity {
#Transient
#JsonIgnore
public abstract Set<String> getProperties();
#Transient
#JsonProperty(access = Access.READ_ONLY)
public abstract String getType();
}
NumberMatchingRule (this is the implementation of matching rule I am testing with)
#Entity
#Data
#Builder
#NoArgsConstructor
#RequiredArgsConstructor
#EqualsAndHashCode(callSuper = false)
public class NumberMatchingRule extends MatchingRule {
public static enum Mode {
LESS, LESS_EQUAL, EQUAL, GREATER_EQUAL, GREATER
}
#Enumerated(EnumType.STRING)
#Builder.Default
private #NonNull Mode mode = Mode.EQUAL;
#NotNull
private #NonNull Double value;
#NotNull
#Size(min = 1)
private #NonNull String property;
#Override
public Set<String> getProperties() {
return Set.of(property);
}
#Transient
#JsonProperty(access = Access.READ_ONLY)
public String getType() {
return "number";
}
}
It all works nicely on MySQL and the stack trace is not giving me any directions on what might be causing the problem. So I am relying on your experience here.
Maybe posting the repo itself is useful, too, since it could container #Query annotations that influence the behavior, but I only have on #Query on the findAll() method that should not be involved. In fact, tests that use findAll() succeed.
#CrossOrigin
public interface TrainingRequestSubscriptionRepo extends CrudRepository<TrainingRequestSubscription, UUID> {
#PreAuthorize("isFullyAuthenticated() and hasAnyScopeFor('trainingrequestsubscription', 'read')")
#Query("SELECT e FROM #{#entityName} e WHERE CONCAT(e.tenant.id, '') IN ?#{security.getTenants('trainingrequestsubscription', 'r')} OR '*' IN ?#{security.getTenants('trainingrequestsubscription', 'r')}")
#Override
Set<TrainingRequestSubscription> findAll();
#PreAuthorize("isFullyAuthenticated() and hasAnyScopeFor('trainingrequestsubscription', 'read')")
#PostAuthorize("hasPermission(returnObject, 'read')")
#Override
Optional<TrainingRequestSubscription> findById(UUID id);
// #formatter:off
#PreAuthorize(
"isFullyAuthenticated() and " +
"(" +
"(#entity.id == null and hasPermission(#entity, 'create'))" + " or " +
"(#entity.id != null and hasPermission(#entity, 'update'))" +
")")
// #formatter:on
#Override
<S extends TrainingRequestSubscription> S save(#Param("entity") S entity);
#PreAuthorize("isFullyAuthenticated() and hasPermission(#entity, 'delete')")
#Override
void delete(#Param("entity") TrainingRequestSubscription entity);
}
EDIT:
As suggested in the comments, I ran HSQLDB in server mode with --silent false and `--trace true`` to see what the incoming request is:
[Server#2e0fa5d3]: 0:SQLCLI:SQLPREPARE insert into number_matching_rule (mode, property, value, id) values (?, ?, ?, ?)
[Server#2e0fa5d3]: 0:SQLCLI:SQLEXECUTE:1
[Server#2e0fa5d3]: 0:SQLCLI:SQLFREESTMT:1
[Server#2e0fa5d3]: 0:SQLCLI:SQLPREPARE insert into training_request_subscription (matching_rule_id, tenant, id) values (?, ?, ?)
[Server#2e0fa5d3]: 0:SQLCLI:SQLEXECUTE:2
[Server#2e0fa5d3]: 0:SQLCLI:SQLFREESTMT:2
[Server#2e0fa5d3]: 0:SQLCLI:SQLENDTRAN:COMMIT
[Server#2e0fa5d3]: 0:HSQLCLI:SETSESSIONATTR:
[Server#2e0fa5d3]: 0:HSQLCLI:SETSESSIONATTR:
[Server#2e0fa5d3]: 0:HSQLCLI:GETSESSIONATTR
[Server#2e0fa5d3]: 0:HSQLCLI:SETSESSIONATTR:
[Server#2e0fa5d3]: 0:SQLCLI:SQLPREPARE select trainingre0_.id as id1_15_0_, trainingre0_.matching_rule_id as matching3_15_0_, trainingre0_.tenant as tenant2_15_0_, matchingru1_.id as id1_2_1_, matchingru1_.mode as mode1_0_1_, matchingru1_.mode as mode1_3_1_, matchingru1_.property as property2_3_1_, matchingru1_.value as value3_3_1_, matchingru1_.ignore_case as ignore_c1_6_1_, matchingru1_.mode as mode2_6_1_, matchingru1_.property as property3_6_1_, matchingru1_.value as value4_6_1_, matchingru1_.clazz_ as clazz_1_, matchingru2_.composite_matching_rule_id as composit1_1_2_, matchingru3_.id as matching2_1_2_, matchingru3_.id as id1_2_3_, matchingru3_.mode as mode1_0_3_, matchingru3_.mode as mode1_3_3_, matchingru3_.property as property2_3_3_, matchingru3_.value as value3_3_3_, matchingru3_.ignore_case as ignore_c1_6_3_, matchingru3_.mode as mode2_6_3_, matchingru3_.property as property3_6_3_, matchingru3_.value as value4_6_3_, matchingru3_.clazz_ as clazz_3_ from training_request_subscription trainingre0_ left outer join ( select id, mode, cast(null as varchar(100)) as property, cast(null as int) as value, cast(null as boolean) as ignore_case, 1 as clazz_ from composite_matching_rule union all select id, mode, property, value, cast(null as boolean) as ignore_case, 2 as clazz_ from number_matching_rule union all select id, mode, property, value, ignore_case, 3 as clazz_ from string_matching_rule ) matchingru1_ on trainingre0_.matching_rule_id=matchingru1_.id left outer join composite_matching_rule_matching_rules matchingru2_ on matchingru1_.id=matchingru2_.composite_matching_rule_id left outer join ( select id, mode, cast(null as varchar(100)) as property, cast(null as int) as value, cast(null as boolean) as ignore_case, 1 as clazz_ from composite_matching_rule union all select id, mode, property, value, cast(null as boolean) as ignore_case, 2 as clazz_ from number_matching_rule union all select id, mode, property, value, ignore_case, 3 as clazz_ from string_matching_rule ) matchingru3_ on matchingru2_.matching_rules_id=matchingru3_.id where trainingre0_.id=?
To me it looks pretty much like what Hibernate logged (see above). To be honest, I don't know what it should look like so it's hard for me to tell what's wrong.
EDIT: #fredt's answer suggested the property column of the number_matching_rule tables was not of type varchar. The following screenshot from the HSQL Database Manager shows is actually is varchar.

The error message is raised in one of the subqueries that contain UNION ALL and is related to the type of the columns named property in the tables such as number_matching_rule. The first SELECT defines the type as varchar(100) and the types of the table column named property is not string (VARCHAR etc.).
For example:
select id, mode, cast(null as varchar(100)) as property, cast(null as int) as value, cast(null as boolean) as ignore_case, 1 as clazz_ from composite_matching_rule
union all
select id, mode, property, value, cast(null as boolean) as ignore_case, 2 as clazz_ from number_matching_rule
union all
select id, mode, property, value, ignore_case, 3 as clazz_ from string_matching_rule
Check the generated tables and see what SQL type is used for the property columns. Also check the other columns for type compatibility.
If the query was hand written, a cast such as CAST(property AS VARCHAR(100)) would convert the column to the required type.
Edit:
You can check the query in DatabaseManager with:
EXPLAIN PLAN FOR select trainingre0_.id ...
If the exception is thrown, try the two subqueries that contain unions separately and simplify until you find which columns cause the exception.

Related

Spring Data JPA query method is not same with the hibernate generated query

I have a simple springboot application where I just query to a User table. I created a repository IUserRepository where I create the query method that returns specific columns only (spring data projections)
User table
columns (indexes: id, lastName, age)
id
firstName
lastName
age
UserProjection
public interface UserProjection {
Integer getId();
String getFirstName();
}
IUserRepository
public interface IUserRepository extends JpaRepository<User, Integer>{
#Query("select u.id, u.firstName from user u where u.lastName = ?1")
public List<UserProjection> findUserByLastName(String lastName);
}
but when I set the hibernate.show_sql=true in my application.properties the result from the hibernate generated query in the logs is
select user0_.id as id_01, user0_.firstName as firstName_01, user0_.lastName as lastName_01, user0_.age as age_01 from user0_ where user0_.lastName = ?
I was expecting to see only the id and firstname since I specified a projection for the return. My expected query should be
select user0_.id as id_01, user0_.firstName as firstName_01 from user0_ where user0_.lastName = ?
am I doing it wrong? or my understanding is just wrong? can some help me understand this scenario. TIA

Is it possible to use Oracle Anonymous Block within the #Query annotation of Spring Data JPA Repository?

I have an Emp table and associated JPA Entity - Employee. Emp table has id, name, and is_active columns.
Also there is an Assets table which has FK reference to Emp table. Assets table has id, emp_id, and name.
I would like to soft delete (update is_active='N') the employee but delete the assets associated using a repository method that reads
public interface EmployeeRepository implements JpaRepository<Employee, Long>{
#Query(
nativeQuery = true,
value="BEGIN"+
" delete from assets where emp_id = :employeeId;"+
" update emp set is_active= 'N' where id = :employeeId;" +
"END"
)
public void inactivate(#Param("employeeId") Long employeeId);
}
The above example is for illustrative purpose. When I try similar approach on my application's entities I am getting errors from Hibernate classes.
PS: I know that there are other approaches like Hibernates's cascade feature etc, but I am specifically interested in the use of Oracle Anonymous Blocks usage.
Tested on spring boot starter data jpa 1.2.5 (hibernate 4.3.10), oracle 12c.
Not work:
#Modifying
#Query(value = "begin insert into T (ID,A) values (:id, :a); exception when dup_val_on_index then update T set A = :a where ID = :id; end;", nativeQuery = true)
void upsertWatchdog(#Param("id") Long id, #Param("a") Date a);
Or:
#Modifying
#Query(value = "begin insert into T (ID,A) values (?1, ?2); exception when dup_val_on_index then update T set A = ?2 where ID = ?1; end;", nativeQuery = true)
void upsertWatchdog(Long id, Date a);
Works:
#Modifying
#Query(value = "begin insert into T (ID,A) values (?1, ?2); exception when dup_val_on_index then update T set A = ?2 where ID = ?1 ; end;", nativeQuery = true)
void upsertWatchdog(Long id, Date a);
Or:
#Modifying
#Query(value = "begin insert into T (ID,A) values (:id, :a); exception when dup_val_on_index then update T set A = :a where ID = :id ; end;", nativeQuery = true)
void upsertWatchdog(#Param("id") Long id, #Param("a") Date a);
Or:
#Modifying
#Query(value = "begin insert into T (ID,A) values (?, ?); exception when dup_val_on_index then update T set A = ? where ID = ?; end;", nativeQuery = true)
void upsertWatchdog(Long id, Date a, Long id2, Date a2);
The crucial part is to use space between named/positioned params and semicolon, because hibernate considers named/positioned param name as id; instead of id.
But I am not sure whether it works because it is a feature or because it is a bug ;-)

Spring Data R2DBC - Building custom postgresql query in reactive repository

I have a table that contains entities with a String id, String jobId, and String status. Given a jobId and a List of ids, I would like to query that table and return a Flux of ids that are not present in the database.
I can do this successfully if I manually execute the following query in pgadmin:
SELECT a.id FROM (VALUES ('20191001_182447_1038'),('abc'),('fdjk')) AS a(id) LEFT JOIN (SELECT * FROM items WHERE job_id = '10a7a04a-aa67-499a-83eb-0cd3625fe27a') b ON a.id = b.id WHERE b.id IS null
The response comes back with only the ids that are not present, 'abc' and 'fdjk'.
In my spring data repo, I define the following method:
#Query("SELECT a.id FROM (VALUES (:ids)) AS a(id) LEFT JOIN (SELECT * FROM items WHERE job_id = :jobId) b ON a.id = b.id WHERE b.id IS null")
Flux<ItemId> getNotContains(#Param("jobId") String jobId, #Param("ids") Collection<String> ids);
The problem is, when I run the code, the query gets expanded to:
SELECT a.id FROM (VALUES ($1, $2, $3)) AS a(id) LEFT JOIN (SELECT * FROM items WHERE job_id = $251) b ON a.id = b.id WHERE b.id IS null]
This always returns a single value because the values are being grouped into a single set of parenthesis instead of wrapping each element of my collection in parenthesis. Just curious if there is a way to handle this properly.
EDIT
Entity class is:
#Data
#Table("items")
public class Item implements Persistable {
#Id
private String id;
private String jobId;
private String customerId;
private Date queuedDate;
private Date lastUpdated;
private String destination;
private String type;
private Status status;
}
Also, my repo is:
public interface ItemRepository extends R2dbcRepository<Item, String>
R2dbcRepository doesn't currently support the fancy magic of more mature spring data repos, so you can't do things like findByJobId and have it auto-gen the query for you.
You can enforce parenthesis by wrapping your arguments into a Object[] to render parameters as expression list.
interface MyRepo {
#Query(…)
Flux<ItemId> getNotContains(#Param("jobId") String jobId, #Param("ids") Collection<Object[]> ids);
}
MyRepo myRepo = …;
Collection<Object[]> ids = Arrays.asList(new Object[]{"1"}, new Object[]{"2"});
myRepo.getNotContains("foo", ids);
See also:
NamedParameterUtils Javadoc

How to avoid N+1 queries for primary entities, when using Custom DTO Object

Have a simple DTO Object as follows,
#BatchSize(size=100)
public class ProductDto {
#BatchSize(size=100)
private Product product;
private UUID storeId;
private String storeName;
public ProductDto(Product product, UUID storeId, String storeName) {
super();
this.product = product;
this.storeId = storeId;
this.storeName = storeName;
}
// setters and getters ...
}
Have Spring Data Repository as follows,
public interface ProductRepository extends CrudRepository<Product, java.util.UUID>
{
#BatchSize(size=100)
#Query("SELECT new com.app1.dto.ProductDto(c, r1.storeName, r1.id) "
+ "FROM Product c left outer join c.storeRef r1")
public Page<ProductDto> findProductList_withDTO(Pageable pageable);
}
When the code runs, it executes one SQL for loading Product ID and StoreName and StoreId
SELECT product0_.id AS col_0_0_,store1_.store_name AS col_1_0_,
store1_.id AS col_2_0_ FROM Product product0_
LEFT OUTER JOIN Store store1_ LIMIT 10
But the problem is next line,
For each Product Row that exists in Database, it's executing SQL Query, instead of using IN to batch load all matching Products in 1 SQL.
For each products it executes following,
SELECT product0_.id AS id1_20_0_, product0_.prd_name AS prd_nam15_20_0_ FROM Product product0_ WHERE product0_.id = ?
SELECT product0_.id AS id1_20_0_, product0_.prd_name AS prd_nam15_20_0_ FROM Product product0_ WHERE product0_.id = ?
Question,
How can we inform hibernate to load all Product Rows in Single SQL Query? I have tried adding #BatchSize(size=100) at all places. Still it's executing multiple queries to load Product Data.
Any hints/solution will be appreciated.
Thanks
Mark

How to use JpaRepository operations (findOne) when having a native query with union all statment

I need to use JpaRepository with a query that has an union statement inside. Is it possible? This is my current implementation and the error I get so far:
Entity:
#Entity
public class PaymentDetails {
#Id
#Column
private String id;
#Enumerated(EnumType.STRING)
#Column(name = "rowtype")
private PaymentType paymentType;
#Embedded
private CardDetails cardDetails;
#Embedded
private BankAccountDetails bankAccountDetails;
Interface and query:
public interface PaymentsRepositoryManagerPayment1 extends JpaRepository<PaymentDetails,String> {
#Query(value = "select id,'CARD' as rowtype, cardnumber, cardtype, nameoncard, expirydate, startdate, securitycode, semafonecr, issuenumber, "
+ "null accountnumber, null accountholdername, null sortcode, null bic, null iban, null currency from pcs.BsbTempCardDetails "
+ "where id = :id union all select id, 'BANK' as rowtype, null cardnumber, null cardtype, null nameoncard, null expirydate, null startdate, "
+ "null securitycode, null semafonecr, null issuenumber, accountnumber, accountholdername, sortcode, bic, iban, currency "
+ "from pcs.BsbTempBankAccountDetails where id = :id", nativeQuery = true) <br>
List< PaymentDetails > findPaymentDetails(#Param("id") String id);
Call:
#Autowired private PaymentsRepositoryManagerPayment1 paymentsRepositoryManagerPayment1;
#Transactional(value = "paymentsRepositoryTransactionManager")
public PaymentDetails retrievePaymentDetailsById1(String id) {
return paymentsRepositoryManagerPayment1.findOne(id);
}
ERROR:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not load an entity: [com.bskyb.repository.payments.model.PaymentDetails#cardId]; SQL [select paymentdet0_.id as id2_0_, paymentdet0_.accountholdername as accounth2_2_0_, paymentdet0_.accountnumber as accountn3_2_0_, paymentdet0_.bic as bic2_0_, paymentdet0_.currency as currency2_0_, paymentdet0_.iban as iban2_0_, paymentdet0_.sortcode as sortcode2_0_, paymentdet0_.cardnumber as cardnumber2_0_, paymentdet0_.cardtype as cardtype2_0_, paymentdet0_.expirydate as expirydate2_0_, paymentdet0_.issuenumber as issuenu11_2_0_, paymentdet0_.nameoncard as nameoncard2_0_, paymentdet0_.securitycode as securit13_2_0_, paymentdet0_.startdate as startdate2_0_, paymentdet0_.rowtype as rowtype2_0_ from PaymentDetails paymentdet0_ where paymentdet0_.id=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not load an entity: [com.bskyb.repository.payments.model.PaymentDetails#cardId]
java.sql.SQLSyntaxErrorException: ORA-00942: table or view does not exist
All of what you showed doesn't seem to be too related to the exception you see:
The exception complaints about a table not being available. Make sure PaymentDetails exists when you issue the query (this is probably the reason you see the exception).
You call findOne(…). Thus the query declaration on findPaymentDetails(…) doesn't play into the use case at all.

Resources