Bind array param to native query - java

I have table product_spec_entry with following columns:
product_spec_id
commodity_spec_id
for one product_spec_id may be several commodity_spec_id, for example:
|product_spec_id | commodity_spec_id|
|----------------|------------------|
|1683 |1681 |
|1692 |1693 |
|1692 |1681 |
|1692 |1687 |
|1692 |1864 |
|1860 |1681 |
|1868 |1681 |
|1868 |1864 |
I want get all product_spec_id that have all commodity_spec_id are passed as parameter.
I wrote next query:
SELECT ps.product_spec_id, commodities
FROM (
SELECT
product_spec_id,
array_agg(commodity_spec_id) AS commodities
FROM system.product_spec_entry
GROUP BY product_spec_id) ps
WHERE Cast(ARRAY [1681, 1864] as BIGINT[]) <# Cast(ps.commodities as BIGINT[]);
It's work fine, and return expected result:
product_spec_id = 1692, 1868
I try use this query for JPA native query:
String query = "SELECT ps.product_spec_id " +
"FROM ( " +
" SELECT " +
" product_spec_id, " +
" array_agg(commodity_spec_id) AS commodities " +
" FROM system.product_spec_entry " +
" GROUP BY product_spec_id) ps " +
"WHERE CAST(ARRAY[:commoditySpecIds] AS BIGINT[]) <# CAST(ps.commodities AS BIGINT[])";
List<Long> commoditySpecsIds = commoditySpecs.stream().map(Spec::getId).collect(Collectors.toList());
List<BigInteger> productSpecIds = em.createNativeQuery(query).setParameter("commoditySpecIds", commoditySpecsIds)
.getResultList();
It does not work because I get array of record (ARRAY[(1692, 1868)]) instead array of bigint (ARRAY[1692, 1868])
How I should bind array param to my query? May be I can use more simple query for it.

Leave out the array[...] from your SQL:
WHERE CAST(:commoditySpecIds AS BIGINT[])
and then pass the list of IDs as a string that looks like this:
"{1,2,3,4}"
The default toString() for Lists usually returns something like: "[1,2,3]", so you could do something like this:
String literal = commoditySpecsIds.toString();
literal = "{" + literal.substring(1,literal.length() - 1) + "};
and then pass that to your obfuscation layer:
setParameter("commoditySpecIds", literal)

I'm exactly in the same situation. Hope #VladMihalcea can help us
Edit
I figure it out to do it with JPA. After reading the impementation of setParameter, i discovered something similar to UserType, the TypedParameterValue.
When you use
setParameter("commoditySpecIds", new TypedParameterValue(IntArrayType.INSTANCE, commoditySpecsIds))
Where IntArrayType.INSTANCE come from "hibernate-types" librairy provided by Vlad Mihalcea. Be carefull, the "commoditySpecsIds" must be an array, not a Collection.
Hope that helps

Other approcah for your case is unwrap the JPA provider session and use some methods form the JPA provider API.
If you are using hibernate, you can do the follow:
// unwrap hibenate session
final Session hibernateSession = em.unwrap(Session.class);
// create you SQL query in hibernate style
final SQLQuery sqlQuery = hibernateSession.createSQLQuery(sql);
And then set the parameter also using hibernate API
final Type customType = new CustomType(new ArrayTypeUser());
sqlQuery.setParameter("commoditySpecIds", value, customType);
Where "ArrayTypeUser" is a custom type that maps PostgresSQL array type to a Java array type.
This is not the best solution, but as you is are already using native queries, maybe for this particular case the best solution is skip the JPA standard API and use the JPA provide API.

Related

spring-data-jdbc: query containing entity with a 1-n relation

How can I write Queries for entities containing a 1-n reference?
Based on the spring-data-jdbc examples I will explain it with the following unit-test:
#Test
public void customQuery_ReferenceMultipleInstances() {
// prepare
LegoSet smallCar = createLegoSet("Small Car 01", 5, 12);
smallCar.setManual(new Manual("Just put all the pieces together in the right order", "Jens Schauder"));
smallCar.addModel("suv", "SUV with sliding doors.");
smallCar.addModel("roadster", "Slick red roadster.");
repository.save(smallCar);
// execute
List<LegoSet> actual = repository.findByName("Small Car 01");
Iterable<LegoSet> compare = repository.findAll();
// verify
assertThat(actual).as("same number of lego sets").hasSize(Lists.newArrayList(compare).size());
assertThat(actual.get(0).getModels()).as("same number of models").hasSize(Lists.newArrayList(compare).get(0).getModels().size());
assertThat(actual.get(0).getModels().get(0)).as("model must not be null").isNotNull();
assertThat(actual.get(0).getModels().get(0).getName()).as("model must have a name").isNotEmpty();
}
This models a LegoSet referencing 2 Models. The repository.findByName() is annotated with a custom query; the repository.findAll() is the standard spring-boot-data method of the CrudRepository (just as reference).
The custom query in version 1:
#Query("SELECT ls.id, ls.name, ls.min_age, ls.max_age, " +
"h.handbuch_id AS manual_handbuch_id, h.author AS manual_author, h.text AS manual_text " +
"FROM lego_set ls JOIN handbuch h ON ls.id = h.handbuch_id " +
"WHERE name = :name")
List<LegoSet> findByName(#Param("name") String name);
In this version the test fails w/
java.lang.AssertionError: [model must not be null]
Expecting actual not to be null
Okay, after that I add another JOIN to model:
#Query("SELECT ls.id, ls.name, ls.min_age, ls.max_age, " +
"h.handbuch_id AS manual_handbuch_id, h.author AS manual_author, h.text AS manual_text, " +
"m.* " +
"FROM lego_set ls JOIN handbuch h ON ls.id = h.handbuch_id " +
"JOIN model m ON ls.id = m.lego_set " +
"WHERE name = :name")
List<LegoSet> findByName(#Param("name") String name);
Now the test fails w/
java.lang.AssertionError: [same number of lego sets]
Expected size:<1> but was:<2> in:
<[LegoSet(id=1, name=Small Car 01, minimumAge=P5Y, maximumAge=P12Y, manual=Manual(id=1, author=Jens Schauder , text=Just put all the pieces together in the right order), models={suv=Model(name=suv, description=SUV with sliding doors.), roadster=Model(name=roadster, description=Slick red roadster.)}),
LegoSet(id=1, name=Small Car 01, minimumAge=P5Y, maximumAge=P12Y, manual=Manual(id=1, author=Jens Schauder
So how do I have to write that query correctly?
The first query is actually the correct one and it is working fine.
The problem is in your test. models is a Map, but you treat it like a List which compiles because a list index is also a valid map key.
If you change the last two assertions in your test like this they will succeed:
assertThat(actual.get(0).getModels().get("suv")).as("model must not be null").isNotNull();
assertThat(actual.get(0).getModels().get("suv").getName()).as("model must have a name").isNotEmpty();
// ----------------------------------------^
An alternative is to use the second query but with a custom ResultSetExtractor to collect multiple rows for the multiple models into a single LegoSet.
I'm in a similar situation, but i don't see what is different in my situation.
I have three models:
Client: id, name..
Project: id, name, clientId, List
ProjectMember
Here is my had coded query
#Query("""
select
project.id,
project.legacy_id,
project.code,
project.client_id,
project.is_archived,
project.name,
project.budget_type,
project.hours_budget_max,
project.money_budget_max
from project
join client on client.id = project.client_id
where client.name ilike '%:name%'
""")
List<Project> findByClientNameContainingIgnoreCase(final String name);
As you can see i'm looking for project where client name (using a join) match a pattern.
Executing the request by hand give me the expected results. But going through spring data jdbc give me no results

JdbcTemplate returns empty list

JdbcTemplate is returning an empty list when executing the "query" method.
public List<Loan> getLoanDAO(Employee employee, String s) {
final String SQL = "SELECT CTLPCODCIA, CTLPCODSUC, CTLPCODTRA, EMPNOMBRE, EMPAPATERN, EMPAMATERN, CTLPCODPRE, "
+ "CTLPTIPPRE, TIPDESPRE, CTLPMONEDA, CTLPESTADO, CTLPMONTOP, CTLPNROCUO, CTLPCUOTA, FLAGTIPOCUOTA, CTLGLOSA, CTLDIASFR, "
+ "CTLDOCADJ, CTLUSUCREA, CTLFECCREA "
+ "FROM HR_CTLPREC_SS INNER JOIN HR_EMPLEADO ON CTLPCODCIA=EMPCODCIA AND CTLPCODSUC=EMPCODSUC AND CTLPCODTRA=EMPCODTRA "
+ "INNER JOIN HR_TIPPRE ON CTLPCODCIA=TIPCODCIA AND CTLPCODSUC=TIPCODSUC AND CTLPTIPPRE=TIPCODPRE "
+ "WHERE TIPFLGEST = '1' AND TIPSELFSERVICE = '1' "
+ "AND CTLPCODCIA = ? AND CTLPCODSUC = ? AND EMPCODTRAJEF = ? AND CTLPESTADO = ? ";
List<Loan> loans = jdbcTemplate.query(SQL, new Object[] {
employee.getCTLPCODCIA(), employee.getCTLPCODSUC(), employee.getCTLPCODTRA(), s }, loanMapper);
return loans;
}
However, when replacing the "?" with the same parameters used in execution and executing in sqldeveloper, it returns 4 rows. I don't know what is wrong since I've been doing de data access code in the same way for all the other entities.
Problem solved
As stated by #Julian:
JdbcTemplate is a proved spring component used by a huge number of applications so in my opinion it must be a bug in your code.
It was not a problem from JdbcTemplate, neither my code. It was an issue from the IDE. I just build my project from scratch using maven console commands and the code worked as intended.
Thanks folks.
JdbcTemplate is a proved spring component used by a huge number of applications so in my opinion it must be a bug in your code.
Not sure what version of Spring you are using but jdbcTemplate.query would expect a Loan Mapper class as one of its arguments. There is no such a mapper present in your code.
I suggest you put a breakpoint just before the query and inspect the employee fields and see if they match the values you are playing in the sqldeveloper.
One other thing that it attracts my attention is the third one where u have EMPCODTRAJEF = ? in the query definition but you use employee.getCTLPCODTRA() as the argument. Obviously I don't know your data model but should it rather be employee.getEMPCODTRAJEF() or the other way around?
If this won't work double check your arguments.
final String SQL = "SELECT CTLPCODCIA, CTLPCODSUC, CTLPCODTRA, EMPNOMBRE, EMPAPATERN, EMPAMATERN, CTLPCODPRE, "
+ "CTLPTIPPRE, TIPDESPRE, CTLPMONEDA, CTLPESTADO, CTLPMONTOP, CTLPNROCUO, CTLPCUOTA, FLAGTIPOCUOTA, CTLGLOSA, CTLDIASFR, "
+ "CTLDOCADJ, CTLUSUCREA, CTLFECCREA "
+ "FROM HR_CTLPREC_SS INNER JOIN HR_EMPLEADO ON CTLPCODCIA=EMPCODCIA AND CTLPCODSUC=EMPCODSUC AND CTLPCODTRA=EMPCODTRA "
+ "INNER JOIN HR_TIPPRE ON CTLPCODCIA=TIPCODCIA AND CTLPCODSUC=TIPCODSUC AND CTLPTIPPRE=TIPCODPRE "
+ "WHERE CTLPCODCIA=? AND CTLPCODSUC = ? AND EMPCODTRAJEF = ? AND CTLPESTADO = ? "
+ "AND TIPFLGEST='1' AND TIPSELFSERVICE='1'";
Add this to application.properties to debug your query.
logging.level.org.springframework.jdbc.core = TRACE

How can I create a new table in a Hibernate Java web service without introducing a SQL injection flaw?

I have a Java web service that uses Hibernate. One of its methods is designed to create a new table in SQL Server, and that table isn't mapped to an object. The current design accepts the database name, schema name, table name, and the field definitions as arguments, and executes creates a query string from them, and then executes it. It works fine, but it is a SQL injection flaw.
Is there a way to create a table without introducing this flaw, for instance using a parameterized query, or using some feature in Hibernate I don't know about?
The flaw happens at the call to createSQLQuery:
String sSql = "CREATE TABLE [" + sDatabaseName + "].[" + sSchema + "].[" + sTableName + "] (" + sSqlFields + ")";
Session session = getSession();
Query q = session.createSQLQuery(sSql);
q.executeUpdate();
It's possible. You can parameterise a query like:
String sSql = "CREATE TABLE [ ? ].[ ? ].[ ? ] (?)";
Query q = session.createQuery(sSql);
q.setString(0, sDatabaseName)
.setString(1, sSchema)
.setString(2, sTableName)
.setString(3, sSqlFields )
.executeUpdate();

how to write a php code interminal

I am getting this exception from fetching record through database based on column portfolio
Query:
String queryString= "select entity from " +Name + " entity WHERE entity."+Column+" = "+ searchId.toString();
Query query = _em.createQuery(queryString);
Many things are wrong here and a lot of important information is missing (entity code?).
className in your example should be just Portfolio instead of class com.hexgen.orm.Portfolio (assuming Portfolio is entity's name, which doesn't have to be the same as class name). If you are using someEntity.getClass().getName() to get it, change to someEntity.getClass().getSimpleName()
searchColumn should be the field name of Portfolio class, not column name in the database. Asssuming PORTFOLIO column is mapped to portfolio field, it should be portfolio.
String queryString = "select entity from " + simpleClassName + " entity WHERE entity." + searchColumn + " = :searchId";
Query query = _em.createQuery(queryString);
query.setParameter("searchId", searchId.toString());
If you don't switch to using query parameters, searchId.toString() should be enclosed in single quotes.
So, the valid query should look something like this
select entity from Portfolio entity WHERE entity.portfolio = 'HEXAGON20'
You have several problems in your query syntax and if you look at the message it's quite explicit:
You build your query with
"select entity from " +className + " entity WHERE entity."+searchColumn+" = "+ searchId.toString();
Which gives
select entity from class com.hexgen.orm.Portfolio entity WHERE entity.PORTFOLIO = HEXAGON20
1- the class should not be there. Ir looks like it comes from the variable className.
2- HEXAGON20 is a string value that should be 'HEXAGON20'

Error: Cannot create TypedQuery for query with more than one return

I try to do the function searchBook with java and jpa. I have 2 classes which are Media and Book. Book extends Media. And I keep the data in the different table. I try to select the data from the query below:
TypedQuery<Media> query = em.createQuery(
"SELECT m.title, b.isbn, b.authors"
+ " FROM Book b, Media m" + " WHERE b.isbn = :isbn"
+ " OR lower(m.title) LIKE :title"
+ " OR b.authors LIKE :authors", Media.class);
query.setParameter("isbn", book.getisbn());
query.setParameter("title", "%" + book.getTitle().toLowerCase()
+ "%");
query.setParameter("authors", "%" + book.getAuthors() + "%");
bookList = query.getResultList();
But I got the error:
java.lang.IllegalArgumentException: Cannot create TypedQuery for query
with more than one return
This is the first time I use JPA. I can't find the the mistake.
As a workaround, to get entity composed by other entity attributes, you can create it within query, providing constructor for it.
Query :
TypedQuery<Media> query = em.createQuery("SELECT NEW package_name.Media(m.title, b.isbn, b.authors)"
+ " FROM Book b, Media m"
+ " WHERE b.isbn = :isbn"
+ " OR lower(m.title) LIKE :title"
+ " OR b.authors LIKE :authors", Media.class);
Entity :
public Media(String title, int isbn, String author){
//-- Setting appropriate values
}
I have provided sample, change the datatypes of the constructor accordingly.
Without goind into details about how Media and Book should be modeled, I will at least explain why you get this exception.
You're doing:
em.createQuery(someJPQL, Media.class);
This means: create a query using someJPQL, and this query will return instances of the Media entity.
But your JPQL is:
SELECT m.title, b.isbn, b.authors ...
So the query does not return entities of type Media. It returns three fields, from two different entities. There is no way your JPA engine could magically create instances of Media from these 3 columns. A query would return instances of Media if it looked like this:
select m from Media m ...
If you still want to use TypedQuery you can change the result type to Object[].
List<Object[]> results = entityManager
.createQuery("SELECT m.title, b.isbn, b.authors ...", Object[].class)
.getResultList();
Each Object[] in the List represents a row of data. It contains the selected values for that row in the order in which they were selected in the query. Element 0 is the title, element 1 is the ISBN, and element 2 is the authors. You'll likely need to cast those values if you want to use them in a meaningful way. Since the field values come from two different tables, you could store them in some kind of container object.
List<MediaContainer> mediaList = new ArrayList<>();
for (Object[] row : results) {
MediaContainer container = new MediaContainer();
container.setTitle((String) row[0]);
container.setIsbn((int) row[1]);
container.setAuthors((String) row[2]);
mediaList.add(container);
}
#WebUser instead of doing
List<EntityIDKey> companies =
getEntityManager().createQuery(sql, EntityIDKey.class).getResultList();
Try this :
List<EntityIDKey> companies =
(List<EntityIDKey>)getEntityManager().createQuery(sql).getResultList();
works for me.
if your are using Hibernate version < 4, you can meet this bug.
I go same problem with v3.5. Finally i had to use simple Query and cast each parameter manually
see other comments here : https://groups.google.com/forum/#!topic/axonframework/eUd1d4rotMY
I... remove
Media.class
of
createQuery
because you return more Entities in this source "SELECT m.title, b.isbn, b.authors"
Ex.:
TypedQuery<Media> query = em.createQuery(
"SELECT m.title, b.isbn, b.authors"
+ " FROM Book b, Media m" + " WHERE b.isbn = :isbn"
+ " OR lower(m.title) LIKE :title"
+ " OR b.authors LIKE :authors");
You can resolve the issue by doing this kind of query:
em.createQuery( "SELECT m"
+ " FROM Book b, Media m" + " WHERE b.isbn = :isbn"
+ " OR lower(m.title) LIKE :title"
+ " OR b.authors LIKE :authors", Media.class);
but this works only if you need fields only from one of the requested tables

Categories