Hibernate - Use native query and alias to Bean with enum properties? - java

I am having trouble using a native query in hibernate to alias a bean that contains enum properties. I am getting an InvocationTargetException when query.list() is called. My example is below:
#Entity(name = "table1")
public class Class1 {
#Column(name = "col1")
#NotNull
private Integer prop1;
#Column(name = "col2")
#NotNull
private String prop2;
#Column(name = "col3", length = 6)
#Enumerated(value = EnumType.STRING)
private MyEnumType prop3;
// ... Getters/Setters...
}
public List getClass1List(){
String sql = "select col1 as prop1, col2 as prop2, col3 as prop3 from table1";
Session session = getSession(Boolean.FALSE);
SQLQuery query = session.createSQLQuery(sql);
query.addScalar("col1", Hibernate.INTEGER);
query.addScalar("col2", Hibernate.STRING);
query.addScalar("col3", Hibernate.STRING);
query.setResultTransformer(Transformers.aliasToBean(Class1.class));
return query.list();
}
During the query.addScalar("col3", Hibernate.STRING) call, I don't know what type to use for col3 (the enum type). Hibernate.String is not working! I have also tried to leave the type off entirely ( query.addScalar("col3") ) but I get the same InvocationTargetException. Can anyone help me out with this? My model cannot be changed and I am stuck with a native sql query. Any ideas are appreciated.

// In public List getClass1List() method:
// 1. DEFINE:
Properties params = new Properties();
params.put("enumClass", "enumerators.MyEnumType");
params.put("type", "12");
// 2. REPLACE:
// query.addScalar("col3", Hibernate.STRING);
// X
query.addScalar("col3", Hibernate.custom(org.hibernate.type.EnumType.class, params));

Firstly, you shouldn't use
private EnumType prop3;
but
private ActualEnum prop3;
Where ActualEnum is your own enum type (for example, Fruits to distinguish apples and oranges).
Second, you hibernate mapping is irrelevant when you use native sql.
Now, there are couple of options I can propose. You can try to use addEntity() instead of bunch of scalars. It's possible that Hibernate will recognize enum property and map correctly.
Other option is to have non public setter that would take string from database, convert it to enum and set actual property.
Finally, you can customize transformer. But it's probably most complex option.

Related

Map result of a sql query to pojo using spring Data JPA

I am a student learning Spring data jpa. I am trying to solve some of practice coding questions. I have a question which i could not find answer to.
I have a database table by name MYDB which has fields:
(id, firstname, lastname, rollno, major, country)
And i have an sql query like this:
select Count(*) as counts, lastname as last_name, major as major_field from MYDB group by country
The above query returns three fields: counts(which is not a db column), last_name and major_field.
I have a POJO like this:
public class MyPojo {
private int counts;
private String lastName;
private String majorField;
// Getters and Setters of all data members here
...................
}
My question is how do i map the result that i got from sql query to my POJO? I need to assign:
counts = counts(from sql query), lastName = last_name(from sql query), majorField = major_field(from sql query).
I am stuck at this point and do not know how to implement further to map result of sql query to POJO:
public interface MyRepo extends JpaRepository<MyPojo, String> {
#Query(value=MY_SQL_QUERY, nativeQuery = true)
List<MyPojo> findAll();
}
Ultimately i need to convert MyPojo to a Json object, but i know how to do that part. I am only stuck without ideas about assigning result of sql query to pojo.
Problem solved using interface-based projections:
https://www.baeldung.com/jpa-queries-custom-result-with-aggregation-functions#solution_interface_jpa
Use the javax.persistence #Column annotation to specify which values from the query are used to populate the fields of the Java object:
public class MyPojo {
#Column(name = "counts")
private int counts;
#Column(name = "last_name")
private String lastName;
... and so on
}
Here is a great tutorial on the annotations:
https://www.javaworld.com/article/3373652/java-persistence-with-jpa-and-hibernate-part-1-entities-and-relationships.html
You have to use TypedQuery and give your class name for executing and mapping your query result into pojos

How to sort on a field in indexedEmbedded

I have an indexedEmbedded object with #OneToMany relation, inside a class, and want to sort with a field containded in that object.
When i call my method for search i got this exception:
"Unexpected docvalues type NONE for field 'employees.id_forSort' (expected=NUMERIC). Use UninvertingReader or index with docvalues"
Thanks in advance!
Here is my code:
public Company {
....
#IndexedEmbedded(prefix = "students.", includeEmbeddedObjectId = true)
#OneToMany(mappedBy = "company")
private Set<Employee> employees= new HashSet<>();
}
public Employee{
....
#SortableField(forField = "id_forSort")
#Field(name = "id_forSort", analyze = Analyze.NO)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
public class CompanySearchRepository{
....
public List<Company> searchCompany(
DataTablePagination pagination, Long id, SearchCriteria searchCriteria) {
FilterField[] filterFields = validateFilterFields(searchCriteria.getFilterFields());
// Sorting
String sortField = "employees.id";
Sort sort = getSort(sortField, pagination.getSortDirection());
Query query;
if (filterFields.length > 0) {
query = getFilterableColumnsQuery(filterFields);
} else {
// global search
query = getGlobalSearchQuery(searchableFields, searchCriteria.getSearchTerm());
}
FullTextQuery fullTextQuery = getFullTextQuery(query);
initPagination(
fullTextQuery,
sort,
pagination.getPage(),
pagination.getPageSize());
List<Company> data = fullTextQuery.getResultList();
}
Sort getSort(String sortField, SortDirection sortDirection) {
SortFieldContext sortFieldContext =
getQueryBuilder().sort().byField(sortField.concat("_forSort"));
return sortDirection.equals(SortDirection.ASC)
? sortFieldContext.asc().createSort()
: sortFieldContext.desc().createSort();
}
}
You named your sortable field employees.id_forSort, but when searching, you're using another field for sorts: employees.id. Use the field that is intended for sorts. Replace String sortField = "employees.id"; with String sortField = "employees.id_forSort"; My bad, I didn't see the weird code that adds a suffix to the field name in the getSort method. Then the message is strange. Would your index be empty, by any chance?
Sorts on multi-valued fields are not supported. You will likely get different results from one execution to the other, since the search engine has to select a value to use for sorts, and which value is selected is undefined.
Regardless of the technical aspect, I'm not sure what you're trying to achieve. You're getting Company instances as a result, and want them to be sorted by Employee ID, of which there are many for each Company. What does it mean? You want the company with the oldest employee first? Something else? If you're just trying to stabilize the order of hits, I'd recommend using the company ID instead.

Save a query result on a custom class

I want to save the result of a query on a List. But when I debug to see the list, it's full of Object[], not a Conta class.
Here is my query and the Conta class.
Query query = em.createQuery("SELECT p.data data, c.nome nomePedido, c.valor, g.nome nomeGarcom FROM Pedidos p\n"
+ " JOIN Cardapio c ON p.idItem = c.id\n"
+ " JOIN Garcons g ON p.idGarcon = g.id", Conta.class);
contaList = query.getResultList();
public class Conta {
private Date data;
private String nomeGarcom;
private String nomePedido;
private float valor;
public Conta(Date data, String nomePedido, float valor, String nomeGarcom) {
this.data = data;
this.nomeGarcom = nomeGarcom;
this.nomePedido = nomePedido;
this.valor = valor;
} ... getters and setters
AFAIK, you can use a constructor expression in JPA, especially since already have a matching constructor:
Query query = em.createQuery("SELECT new com.mine.Conta(p.data, g.nome, c.nome, c.valor) FROM Pedidos p\n" ...
Your Conta class is no Jpa #Entity - that's why it doesn't know how to map the result to your Java Class. Annotate Conta properly, give it a primary key, that's the most straight-forward way.
#Entity
public class Conta {
#Id
private long someID;
#Column
private float valor;
#Column
... more columns (watch out for your Timestamp, probably needs #Temporal)
....getters and setters
}
As is, you can't use Jpa to save instances of your Conta into the database which is probably one thing you're looking to do later.
Consider this Answer too: JPA : How to convert a native query result set to POJO class collection
Leandro, can you also please attach the code of getResultList()? From my experience, there are two potential causes. One, your getResultList method has the wrong return type. Two, your query is not working at all.

Spring Data Cassandra: InvalidQueryException: UUID should be 16 or 0 bytes (20)

Entity
#Builder
#Getter #Setter
#ToString(doNotUseGetters = true)
#EqualsAndHashCode(doNotUseGetters = true)
#Table(value = "entity")
public class Entity implements Serializable {
#PrimaryKeyColumn(name = "id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
#CassandraType(type = DataType.Name.UUID)
private UUID Id;
#Column("list_name")
#CassandraType(type = DataType.Name.TEXT)
private String name;
#Column("type")
#CassandraType(type = DataType.Name.TINYINT)
private Byte type;
Entity Repo
#Repository
public interface EntityRepo extends BaseRepo<Entity, UUID> {
#Query("SELECT * FROM entity WHERE id IN (:id)")
Collection<ListEntity> findByIds(#Param("id") Collection<UUID> listIds);
#Query("SELECT * FROM entity WHERE list_id = :id")
ListEntity findById(#Param("id") UUID id);
}
Query
listRepo.findByListId(UUIDs.random())
listRepo.findByListIds(Arrays.asList(UUIDs.random())
Both results in
CassandraInvalidQueryException/InvalidQueryException
org.springframework.data.cassandra.CassandraInvalidQueryException: Query; CQL
[SELECT * FROM lists WHERE list_id IN (?)]; UUID should be 16 or 0 bytes (20);
nested exception is com.datastax.driver.core.exceptions.`InvalidQueryException: UUID should be 16 or 0 bytes (20)
Anything missing here? Can somebody help?
It looks like as if IN queries using UUID are not possible by passing in a collection into a single bind value.
Here's what happens:
Spring Data transforms your String-query
SELECT * FROM entity WHERE id IN (:id)
into Cassandra's notation using positional query arguments
SELECT * FROM entity WHERE id IN (?)
and binds the given parameter to ?. Let's transform this into an executable piece of code:
session.execute(new SimpleStatement("SELECT * FROM entity WHERE id IN (?)",
Arrays.asList(UUIDs.random())));
When you run this snipped, then you'll end up with InvalidQueryException again. We can still make an IN query happen, but we need to unfold parameters:
new SimpleStatement("SELECT * FROM entity WHERE id IN (?, ?)", UUIDs.random(), UUIDs.random())
In an ideal world, Cassandra could accept a List (or Set) of items to keep the query string agnostic against the actual number of arguments.
It is work at me.
Use: findAllBy[object-property]In where [object-property] is name of property of the MyModel UUID.
#Table("example")
public class MyModel {
#PrimaryKeyColumn(name = "uuid", type = PrimaryKeyType.PARTITIONED)
private UUID uuid;
#Column("...")
...
}
public interface MyRepository extends CassandraRepository<MyModel, UUID> {
List<Restaurant> findAllByUuidIn(Collection<UUID> uuidButIfYouWant);
}
// It is OK
return this.myRepository.findAllByUuidIn(Set.of(
UUID.randomUUID(),
UUID.randomUUID()
));
// Throw Error: no viable alternative at input '<EOF>' (SELECT * FROM example WHERE [uuid] IN...)
return this.myRepository.findAllByUuidIn(Set.of());
So if not contain uuid:
Collection<UUID> search = Set.of();
if(search.size() == 0) {
return List.of();
}
return this.myRepository.findAllByUuidIn(search);
(I am adding this answer for future reference)
I had the same error when running a spark application.
The error occurred when executing a Cassandra query and where a UUID type was expected, some other data type was sent by the query. In my case, it was a String. Passing the right data type (UUID instead of String) resolved the issue.
I hope you found an answer, but if you are using UUID version 4, maybe the type in your table are wrong. For example instead of UUID your ID field could be varchar.

partially mapping the result of a NamedNativeQuery to a class

I have an #Entity class Person, that has multiple fields and I would like to map the result of several #NamedNativeQuerys to the Person class however, the queries I am running do not return values for every field in the Person class. When I try to run a query I get the following errors:
[error] o.h.u.JDBCExceptionReporter - Invalid column name bar.
[error] play - Cannot invoke the action, eventually got an error: javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not execute query
My class is set up similar to this:
#NamedNativeQueries({
#NamedNativeQuery(
name = "getBirthdate",
//Returns values for {idnumber, name, birthdate}
query = "EXEC dbo.proc_get_birthdate :name",
resultClass = Person.class
),
#NamedNativeQuery(
name = "getBar",
//Returns values for {idnumber, name, bar}
query = "EXEC dbo.proc_get_bar :name",
resultClass = Person.class
)
})
#Entity
public class Person {
#Id
#Column(name = "idnumber")
private int idNumber;
#Column(name = "name")
private String name;
#Column(name = "birthdate")
private String birthdate;
#Column(name = "foo")
private String foo;
#Column(name = "bar")
private String bar;
//All appropriate getter and setter methods are implemented
}
I double checked and all the columns in the Person class do, in fact, exist in the table being queried.
My actual class is much larger and due to that and some security concerns, I do not want my queries to have to return EVERY field and am hoping that there is a simple way to just give them a value of null if a value isn't returned by the query. I tried to set each field to null in the declaration (example below) but that didn't work either.
#Column(name = "bar")
private String bar = null;
I would really rather not have to create a tailored class for every single query I need to run so if what I'm trying to do is possible, any help would be greatly appreciated. Thanks in advance.
I believe that you misunderstood the #NamedNativeQuery use. This annotation should be used with a SQL Query and not with a stored procedure. Looking at the exception SQLGrammarException: could not execute query it is possible to see that is a grammar exception, the query could not be parsed.
With JPA 2.1 you could use the annotation #NamedStoredProcedureQuery that is the one with support to stored procedures.

Categories

Resources