Using function in where clause with clob parameter - oracle

We are using a #NamedNativeQuery to fetch entities from our database that are qualified by the stored procedure flexmatch in the where clause of a query.
This works fine in general, but when the parameter chimeString exceeds 4.000 characters it fails raising the following exception:
ORA-01460: unimplemented or unreasonable conversion requested
This does make sense, as 4.000 characters are Oracle's border between String and Clob.
We tried to
use org.hibernate.engine.jdbc.ClobProxy
return entityManager
.createNamedQuery("Structure.findByExactMatch", Structure.class)
.setParameter("chime", ClobProxy.generateProxy(chimeString))
.getResultList();
use javax.persistence.criteria.ParameterExpression together with org.hibernate.engine.jdbc.ClobProxy
ParameterExpression<Clob> chimeParam = entityManager
.getCriteriaBuilder()
.parameter(Clob.class, "chime");
return entityManager
.createNamedQuery("Structure.findByExactMatch", Structure.class)
.setParameter(chimeParam, ClobProxy.generateProxy(chimeString))
.getResultList();
Libraries & System:
Oracle 11g
Hibernate 3.6.6
The find method.
public List<Structure> findByExactMatch(String chimeString) {
return entityManager
.createNamedQuery("Structure.findByExactMatch", Structure.class)
.setParameter("chime", chimeString)
.getResultList();
}
The Structure entity.
#Entity
#NamedNativeQueries({
#NamedNativeQuery(
name = "Structure.findByExactMatch",
query = "SELECT id, molfile(ctab) ctab FROM structure " +
"WHERE flexmatch(ctab, :chime, 'all')=1",
resultClass = Structure.class) })
public class Structure {
#Id
#Column(name = "ID")
private long id;
#Lob
#Column(name = "CTAB")
private String ctab;
// getter & setter
}
Edit 1 The pl/sql function, as you can see it is overloaded.
FUNCTION flexmatch(
molobj IN BLOB,
querymol IN VARCHAR2,
args IN VARCHAR2)
RETURN NUMBER
FUNCTION Flexmatch(
molobj IN BLOB,
querymol IN CLOB,
args IN VARCHAR2)
RETURN NUMBER

After some days of trying, we gave up to solve it within Hiberante. We ran the query using SpringJDBC, which is also present in the project, and used the ID to populate a Hiberante entity. You could do this with plain old JDBC also.

Related

QueryDSL Predicate for use with JPARepository where field is a JSON String converted using an AttributeConverter to a List<Object>

I have a JPA Entity (Terminal) which uses an AttributeConverter to convert a Database String into a list of Objects (ProgrmRegistration). The converter just uses a JSON ObjectMapper to turn the JSON String into POJO objects.
Entity Object
#Entity
#Data
public class Terminal {
#Id
private String terminalId;
#NotEmpty
#Convert(converter = ProgramRegistrationConverter.class)
private List<ProgramRegistration> programRegistrations;
#Data
public static class ProgramRegistration {
private String program;
private boolean online;
}
}
The Terminal uses the following JPA AttributeConverter to serialize the Objects from and to JSON
JPA AttributeConverter
public class ProgramRegistrationConverter implements AttributeConverter<List<Terminal.ProgramRegistration>, String> {
private final ObjectMapper objectMapper;
private final CollectionType programRegistrationCollectionType;
public ProgramRegistrationConverter() {
this.objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
this.programRegistrationCollectionType =
objectMapper.getTypeFactory().constructCollectionType(List.class, Terminal.ProgramRegistration.class);
}
#Override
public String convertToDatabaseColumn(List<Terminal.ProgramRegistration> attribute) {
if (attribute == null) {
return null;
}
String json = null;
try {
json = objectMapper.writeValueAsString(attribute);
} catch (final JsonProcessingException e) {
LOG.error("JSON writing error", e);
}
return json;
}
#Override
public List<Terminal.ProgramRegistration> convertToEntityAttribute(String dbData) {
if (dbData == null) {
return Collections.emptyList();
}
List<Terminal.ProgramRegistration> list = null;
try {
list = objectMapper.readValue(dbData, programRegistrationCollectionType);
} catch (final IOException e) {
LOG.error("JSON reading error", e);
}
return list;
}
}
I am using Spring Boot and a JPARepository to fetch a Page of Terminal results from the Database.
To filter the results I am using a BooleanExpression as the Predicate. For all the filter values on the Entity it works well, but the List of objects converted from the JSON string does not allow me to easily write an Expression that will filter the Objects in the list.
REST API that is trying to filter the Entity Objects using QueryDSL
#GetMapping(path = "/filtered/page", produces = MediaType.APPLICATION_JSON_VALUE)
public Page<Terminal> findFilteredWithPage(
#RequestParam(required = false) String terminalId,
#RequestParam(required = false) String programName,
#PageableDefault(size = 20) #SortDefault.SortDefaults({ #SortDefault(sort = "terminalId") }) Pageable p) {
BooleanBuilder builder = new BooleanBuilder();
if (StringUtils.isNotBlank(terminalId))
builder.and(QTerminal.terminal.terminalId.upper()
.contains(StringUtils.upperCase(terminalId)));
// TODO: Figure out how to use QueryDsl to get the converted List as a predicate
// The code below to find the programRegistrations does not allow a call to any(),
// expects a CollectionExpression or a SubqueryExpression for calls to eqAny() or in()
if (StringUtils.isNotBlank(program))
builder.and(QTerminal.terminal.programRegistrations.any().name()
.contains(StringUtils.upperCase(programName)));
return terminalRepository.findAll(builder.getValue(), p);
}
I am wanting to get any Terminals that have a ProgramRegistration object with the program name equal to the parameter passed into the REST service.
I have been trying to get CollectionExpression or SubQueryExpression working without success since they all seem to be wanting to perform a join between two Entity objects. I do not know how to create the path and query so that it can iterate over the programRegistrations checking the "program" field for a match. I do not have a QProgamRegistration object to join with, since it is just a list of POJOs.
How can I get the predicate to match only the Terminals that have programs with the name I am searching for?
This is the line that is not working:
builder.and(QTerminal.terminal.programRegistrations.any().name()
.contains(StringUtils.upperCase(programName)));
AttributeConverters have issues in Querydsl, because they have issues in JPQL - the query language of JPA - itself. It is unclear what actually the underlying query type of the attribute is, and whether the parameter should be a basic type of that query type, or should be converted using the conversion. Such conversion, whilst it appears logical, is not defined in the JPA specification. Thus a basic type of the query type needs to be used instead, which leads to new difficulties, because Querydsl can't know the type it needs to be. It only knows the Java type of the attribute.
A workaround can be to force the field to result into a StringPath by annotating the field with #QueryType(PropertyType.STRING). Whilst this fixes the issue for some queries, you will run into different issues in other scenarios. For more information, see this thread.
Although the following desired QueryDsl looks like it should work
QTerminal.terminal.programRegistrations.any().name().contains(programName);
In reality JPA would never be able to convert it into something that would make sense in terms of SQL. The only SQL that JPA could convert it into could be as follows:
SELECT t.terminal_id FROM terminal t where t.terminal_id LIKE '%00%' and t.program_registrations like '%"program":"MY_PROGRAM_NAME"%';
This would work in this use case, but be semantically wrong, and therefore it is correct that it should not work. Trying to select unstructured data using a structured query language makes no sense
The only solution is to treat the data as characters for the DB search criteria, and to treat it as a list of Objects after the query completes and then perform filtering of the rows in Java. Although This makes the paging feature rather useless.
One possible solution is to have a secondary read only String version of the column that is used for the DB search criteria, that is not converted to JSON by the AttributeConverter.
#JsonIgnore
#Column(name = "programRegistrations", insertable = false, updatable = false)
private String programRegistrationsStr;
The real solution is do not use unstructured data when you want structured queries on that data Therefore convert the data to either a database that supports the JSON natively for queries or model the data correctly in DDL.
To have a short answer: the parameter used in the predicate on attribute with #QueryType must be used in another predicate on attribute of type String.
It's a clearly known issue describe in this thread: https://github.com/querydsl/querydsl/issues/2652
I simply want to share my experience about this bug.
Model
I have an entity like
#Entity
public class JobLog {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
#QueryType(PropertyType.STRING)
private LocalizedString message;
}
Issue
I want to perform some predicate about message. Unfortunately, with this configuration, I can't do this:
predicates.and(jobLog.message.likeIgnoreCase(escapedTextFilter));
because I have the same issues that all people!
Solution
But I find a way to workaround :)
predicates.and(
(jobLog.id.likeIgnoreCase(escapedTextFilter).and(jobLog.id.isNull()))
.or(jobLog.message.likeIgnoreCase(escapedTextFilter)));
Why it workaround the bug?
It's important that escapedTextFilter is the same in both predicate!
Indeed, in this case, the constant is converter to SQL in the first predicate (which is of String type). And in the second predicate, we use the conterted value
Bad thing?
Add a performance overflow because we have OR in predicate
Hope this can help someone :)
I've found one way to solve this problem, my main idea is to use mysql function cast(xx as char) to cheat hibrenate. Below is my base info. My code is for work , so I've made an example.
// StudentRepo.java
public interface StudentRepo<Student, Long> extends JpaRepository<Student, Long>, QuerydslPredicateExecutor<Student>, JpaSpecificationExecutor<Student> {
}
// Student.java
#Data
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(of = "id")
#Entity
#Builder
#Table(name = "student")
public class Student {
#Convert(converter = ClassIdsConvert.class)
private List<String> classIds;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
// ClassIdsConvert.java
public class ClassIdsConvert implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> ips) {
// classid23,classid24,classid25
return String.join(",", ips);
}
#Override
public List<String> convertToEntityAttribute(String dbData) {
if (StringUtils.isEmpty(dbData)) {
return null;
} else {
return Stream.of(dbData.split(",")).collect(Collectors.toList());
}
}
}
my db is below
id
classIds
name
address
1
2,3,4,11
join
北京市
2
2,31,14,11
hell
福建省
3
2,12,22,33
work
福建省
4
1,4,5,6
ouy
广东省
5
11,31,34,22
yup
上海市
-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(11) NOT NULL,
`classIds` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
Use JpaSpecificationExecutor solve the problem
Specification<Student> specification = (root, query, criteriaBuilder) -> {
String classId = "classid24"
String classIdStr = StringUtils.wrap(classId, "%");
var predicate = criteriaBuilder.like(root.get("classIds").as(String.class), classIdStr);
return criteriaBuilder.or(predicate);
};
var students = studentRepo.findAll(specification);
log.info(new Gson().toJson(students))
attention the code root.get("classIds").as(String.class)
In my opinion, if I don't add .as(String.class) , hibernate will think the type of student.classIds is list and throw an Exception as below.
SQL will like below which runs correctly in mysql. But hibnerate can't work.
org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [%classid24%] did not match expected type [java.util.List (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [%classid24%] did not match expected type [java.util.List (n/a)]
SELECT
student0_.id AS id1_0_,
student0_.class_ids AS class_ids2_0_
FROM
student student0_
WHERE
student0_.class_ids LIKE '%classid24%' ESCAPE '!'
if you add .as(String.class) , hibnerate will think the type of student.classIds as string and won't check it at all.
SQL will be like below which can run correct in mysql. Also in JPA.
SELECT
student0_.id AS id1_0_,
student0_.class_ids AS class_ids2_0_
FROM
student student0_
WHERE
cast( student0_.class_ids AS CHAR ) LIKE '%classid24%' ESCAPE '!'
when the problem is solved by JpaSpecificationExecutor, so I think this can be solve also in querydsl. At last I find the template idea in querydsl.
String classId = "classid24";
StringTemplate st = Expressions.stringTemplate("cast({0} as string)", qStudent.classIds);
var students = Lists.newArrayList<studentRepo.findAll(st.like(StringUtils.wrap(classId, "%"))));
log.info(new Gson().toJson(students));
it's sql is like below.
SELECT
student0_.id AS id1_0_,
student0_.class_ids AS class_ids2_0_
FROM
student student0_
WHERE
cast( student0_.class_ids AS CHAR ) LIKE '%classid24%' ESCAPE '!'

Calling Stored Procedure having Table/TableType as In/OUT params from Springboot

I am following this guide for calling my stored procedure. I searched around for other resources for an example where IN and OUT parameters are of type table but couldn't find one.
I am having error: Cannot convert SQL type TABLE to Java type java.lang.Object
There are no ways to know what exactly caused the problem. But I'm giving an example of a stored function that actually worked for me. Stored functions could be an alternative approach to stored procedures which need to return DTOs.
#Repository
public interface CustomRepository extends CrudRepository<YourObject, UUID> {
#Query(value = "SELECT * from your_stored_procedure(:key)", nativeQuery = true)
List<YourObject> findAlldata(#Param("key") String key);
}
#Service
public class CustomService {
#Autowired
private CustomRepository customRepository;
public List<YourObject> getAllData(String key) {
List<YourObject> yourObjects = this.customRepository.findAlldata(key);
}
}
And here is the code for the stored function in Postgres. The return objects should have a similar structure for both the repository and the procedure.
CREATE FUNCTION public.your_stored_procedure(key text) RETURNS TABLE(id uuid, name character varying)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY SELECT
id,
name
FROM public.your_table;
END;
$$;

JPA + Hibernate, Error : com.microsoft.sqlserver.jdbc.SQLServerException: The column name value is not valid

I'm trying to use JPA + Hibernate to call a Store procedure with #Query annnotation
#Query(value = "exec dbo.getUserById :Id", nativeQuery = true)
User findUserById(#Param("Id) Long id);
and map the result to the follow entity
#Entity
#Table(name = "users_contact")
public class User{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Integer id;
private String userName;
private String value;
}
Store procedure only return 'id' and 'userName' columns, so when i call findUserById method Spring throws the follow error
Error : com.microsoft.sqlserver.jdbc.SQLServerException: The column name 'value' is not valid
I guess its because Hibernate try to find 'value' property and map to 'value' property in the entity but for this query i dont need value property.
Its a requirement in the proyect that i only use store procedures to access to the database, so i can't use SQL native queries.
Is there any configuration available to specified ignore this property only for the call to this store procedure?
You have:
private String name;
Try changing "name" to say - "somename". This is causing the issue if you look at the exception. "name" is a reserved keyword. For simplicity if your entity is say TheUser, then use something like - username instead of name. I hope you got the idea !

Spring JPA - insert list with batch_size using native query in JpaRepository or CrudRepository

UPDATE: Thank you to #M.Deinum for informing me how to deal with the #ManyToOne cascade issue that I was previously stuck on by using EntityManager getReference or JpaRepository getOne function. I am now able to batch save with basic JpaRepsitorymethods as follows:
#Transactional
public void insertCommands(List<CommandDto> dtos) {
final List<Command> commands = new ArrayList<>();
for (CommandDto dto : dtos) {
ZonedDateTime now = ZonedDateTime.now();
final Request request = requestRepository.getOne(dto.getRequestId());
String commandId = UUID.randomUUID().toString();
final Command command = new Command();
command.setId(commandId);
command.setCreatedBy(SYSTEM);
command.setCreatedTimestamp(now);
command.setStatus(dto.getStatus());
command.setContent(dto.getContent());
command.setSendOnDate(dto.getSendOnDate());
command.setRequest(request);
commands.add(command);
}
commandRepository.saveAll(commands);
}
Original post content as seen below:
I need to insert multiple rows to my application's database using the batch_size property set in my properties:
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
spring.jpa.properties.hibernate.jdbc.batch_size=10
I am looking for a way to insert them using nativequery in JpaRepository or CrudRepository with a syntax along the lines of the following, but allowing for multiple rows:
#Modifying
#Transactional
#Query(nativeQuery = true, value = "INSERT into command (id, created_by, created_timestamp, " +
" updated_by, updated_timestamp, status, content, send_on_date, request_id) " +
"VALUES (:id, :createdBy, :createdTimestamp, :updatedBy, :updatedTimestamp, " +
" :status, :content, :sendOnDate, :requestId) ")
int batchInsertCommandDto( #Param("commands") List<CommandDto> commandDtos);
How can I perform this sort of query with a list?
NOTE: Before you bring up EntityManger, I must note that have not had any luck with saveall functionality because:
I am translating the data from it's JSON-friendly class (CommandDto) to its data entity class(Command)
The class for the entity (Command) has a #ManyToOne annotation for one or more Object fields whereas the JSON object (CommandDto) simply has the id of these fields. For example, java class for command entity has "request" field:
#ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
#JoinColumn(name = "request_id", foreignKey = #ForeignKey(name="fk_rcommand_request_id"))
private Request request;
Whereas CommandDto object simply has field "requestId". That means that if I simply try creating a request object with only the requestId, entityManager will fail to save because the Request object is not fully formed and therefore not recognized. It would be grossly inefficient to retrieve the Request object for each command being saved, so I am looking to do the mapping as seen in the nativequery above.

how to map custom sql query to model to load on jsp view in spring

I am beginner in spring. I want to show SQL data to JSP view page.
This is my SQL table
create table customer(
id int primary key,
name varchar(250),
salary int,
manager_id int
)
and I am trying to show data from this query
select m.id, m.name, m.salary, n.name from customer m, customer n where n.id=m.manager_id
So basically from this query, I am trying to show ID int, name varchar, salary int, manager_name varchar.
I have create the entity java class as below
#Entity
#Table(name="customer")
public class Customer{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#Column
private String name;
#Column(name="manager_id")
private String manager;
#Column(name="salary")
private int salary;
............
............
}
This is the code of my DAO class
Session session= entityManager.unwrap(Session.class);
Query<Employee> query= session.createQuery(<above sql query need to add here?>,Customer.class);
return query.getResultList();
So the issues are,
This SQL query return the data which could not be matched to Customer Entity class. So do I need to create another Entity class for this? Is there any better way?
The above required SQL query is not able to execute. What the correct way to execute custom SQL query?
Regarding your first question: Yes there is better way. you can directly fetch the results in a projection dto class. take a at: https://vladmihalcea.com/the-best-way-to-map-a-projection-query-to-a-dto-with-jpa-and-hibernate/
About your second question: What Do you mean by saying the query does not execute?
Can you give us the Exception/Stacktrace? Did you try to execute the query (sql form of it) physically? Does it run then?

Resources