I have a database table where one of the columns contain an enum
Entity file:
#Entity
#Table(name = "error_log_entry")
public class ErrorLogEntryEntity extends AbstractPersistable<UUID> {
#Id
private UUID id;
#Column(name = "priority")
#Enumerated(EnumType.STRING)
private ErrorLogEntryPriorityType priority;
}
Enum file
public enum ErrorLogEntryPriorityType {
INFO,
WARN,
DANGER
}
Now I am trying to write a query that counts the number of rows where the enum is equal to "DANGER"
public interface ErrorLogEntryRepository extends JpaRepository<ErrorLogEntryEntity, UUID> {
List<ErrorLogEntryEntity> findByChangedUserId(UUID userId);
#Query(nativeQuery = true, value = "select count(*) from error_log_entry where priority = 'DANGER'")
ErrorLogEntryPriorityType getErrorCount();
}
However, this causes the following error
java.lang.ClassCastException: class java.math.BigInteger cannot be cast to class project.core.types.ErrorLogEntryPriorityType
I am a bit new to spring, but from the documentation, this problem seems to occur because I am casting on an incompatible type. I am not 100% sure.
Any suggestions on how to make this query work?
As mentioned in the comments, the error is because the return type of the query method you've defined is incorrect. You're not looking to get back an instance of ErrorLogEntryPriorityType, you need to get back a numerical value - the count of how may records match the query.
Having said that, there's an even easier, cleaner solution - use Spring Data's "derived query method" and you don't even need to write the #Query yourself. For example, this should work as you intend:
long countByPriority(ErrorLogEntryPriorityType priority);
Note there is no #Query annotation; Spring Data understands the mapping of your entity and generates the query for you based on the method name and parameter types.
This method is also more flexible; the application can use it to count all records with any of the values for priority, not just DANGER. Generally speaking, it's good to have the low-level repository methods be building blocks, like Lego. The application can then assemble a wide variety of "things" from those basic building blocks, such as a service method that counts how many DANGER log records there are.
I suggest you read all the reference docs for Spring Data repositories, starting here.
Related
Problem
To make my code cleaner i want to introduce a generic Repository that each Repository could extend and therefore reduce the code i have to have in each of them. The problem is, that the Ids differ from Class to Class. On one (see example below) it would be id and in the other randomNumber and on the other may even be an #EmbeddedId. I want to have a derived (or non derived) query in the respository that gets One by id.
Preferred solution
I Imagine having something like:
public interface IUniversalRepository<T, K>{
#Query("select t from # {#entityName} where #id = ?1")
public T findById(K id);
}
Ecample Code
(that does not work because attribute id cannot be found on Settings)
public interface IUniversalRepository<T, K>{
//should return the object with the id, reagardless of the column name
public T findById(K id);
}
// two example classes with different #Id fields
public class TaxRate {
#Id
#Column()
private Integer id;
...
}
public class Settings{
#Id
#Column() //cannot rename this column because it has to be named exactly as it is for backup reason
private String randomNumber;
...
}
// the Repository would be used like this
public interface TaxRateRepository extends IUniversalRepository<TaxRate, Integer> {
}
public interface SettingsRepository extends IUniversalRepository<TaxRate, String> {
}
Happy for suggestions.
The idea of retrieving JPA entities via "id query" is not so good as you might think, the main problem is that is much slower, especially when you are hitting the same entity within transaction multiple times: if flush mode is set to AUTO (with is actually the reasonable default) Hibernate needs to perform dirty checking and flush changes into database before executing JPQL query, moreover, Hibernate doesn't guarantee that entities, retrieved via "id query" are not actually stale - if entity was already present in persistence context Hibernate basically ignores DB data.
The best way to retrieve entities by id is to call EntityManager#find(java.lang.Class<T>, java.lang.Object) method, which in turn backs up CrudRepository#findById method, so, yours findByIdAndType(K id, String type) should actually look like:
default Optional<T> findByIdAndType(K id, String type) {
return findById(id)
.filter(e -> Objects.equals(e.getType(), type));
}
However, the desire to place some kind of id placeholder in JQPL query is not so bad - one of it's applications could be preserving order stability in queries with pagination. I would suggest you to file corresponding CR to spring-data project.
UPDATE:
I have tried implementing the SQLData interface for my Financial.class and PersonalInfo.class Java POJOs defined on my #NamedStoredProcedureQuery for the type. I also implemented the required getSQLTypeName, readSQL, and writeSQL methods per an Oracle doc:
https://docs.oracle.com/cd/A97335_02/apps.102/a83724/samapp6.htm
I was hoping this would work, but it looks like its still giving me the same Type cannot be null exception. Does it matter that these personal_information_t and financial_t Objects defined in my Oracle DB are inheriting from a superclass, called base_t ?
Hi guys, I'm simply trying to use the #NamedStoredProcedureQuery directly on my #Entity class to call a Stored Procedure that is in my Oracle Database (that is in a separate schema, "JJR"). I can indeed correctly connect to this database programmatically from my Java Spring Boot application, and I can run JPA queries successfully like .findAll() so I know its not a connection issue but I believe something to do with my #NamedStoredProcedure declaration. All the tutorials on Google for Spring JPA Stored Procedure queries are using standard data types like Long or String, and only returning one OUT parameter.
However, I'm returning two OUT parameters and to make it more complicated, they are user-defined types (defined in the Oracle Database), so I'm trying to figure out how to handle this as my current attempt is returning back this exception:
org.springframework.dao.InvalidDataAccessApiUsageException: Type cannot be null; nested exception is java.lang.IllegalArgumentException:
My Stored Procedure structure (It is inside a Package in my oracle db of pkg_employee_data in schema/user JJR). The user-defined types (personal_information_t and financial_t both have multiple fields in them, i.e. personal_information_t has an userid, firstname, lastname, and financial_t has salary, networth etc..:
PROCEDURE get_employee_data (
empid IN emp.emp_id%TYPE, // NUMBER(38,0)
persinfo OUT personal_information_t, // user-defined type
financ OUT financial_t / user-defined type
);
And how I'm defining the #NamedStoredProcedureQuery
#Entity
#NamedStoredProcedureQuery(name = "Employee.getEmployeeData",
procedureName = "pkg_employee_data.get_employee_data", parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "empid", type = Long.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "persinfo", type = PersonalInfo.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "financ", type = Financial.class)})
public class Employee {
// Should I list the columns I want mapped here in the Entity
// from the "persinfo" and "financ" user-defined types, that
// should have a bunch fields/data inside them?
#Id
private long userId;
private String firstName;
private Double salary;
}
And the #Repository declaration, where I map this method:
#Repository
public interface EmployeeRepository extends JpaRepository<Employee,Long> {
#Procedure(name = "Employee.getEmployeeData")
Map<String, Object> getEmployee(#Param("empid") long empid);
}
Are my type = "*.class" declarations even correct on the #NamedStoredProcedureQuery annotation? seems like my Types ares getting read a null. I created a PersonalInfo.java and Financial.java #Entities in my code as I hoped it would map from the Oracle user-defined types but it doesn't seem like that's working.
On all my #Entities, I have to declare some meta-information like #Table(schema = "JJR", name = "MY_TABLE_NAME") because as I stated above, I'm logging into a specific user/schema in my Oracle DB. Do I need to put this on the #NamedStoredProcedureQuery too? I'm not mapping to a table here technically though (I just need the data from the two OUT parameters).
I even tried using java.sql.Struct.class as the Type in the #NamedStoredProcedureQuery per Google, but nothing seems like it's working.
What actually columns/fields do I need defined in the Employee Entity, which has the #NamedStoredProcedureQuery annotation on it? I'm not really mapping to a Table, like normally we use #Entity. This stored procedure is just returning those two OUT parameters (personal_information_t and financial_t and I need the data from it). Should the fields in Employee class be simply the fields that are in the Oracle user-defined types that I need?
I am following the code for the running SQL queries in the Ignite cache, but am able to fully realize the use of the CacheConfiguration.setIndexedTypes API.
I am following the only help that I could find at the ignite site.
The documentation here says to use
CacheConfiguration.setIndexedTypes(MyKey.class, MyValue.class).
Now lets say in the Person class
#QuerySqlField(index = true)
private long id;
#QuerySqlField
private String firstName;
Which are the parameters that I should be passing in the setIndexedType method?
setIndexedTypes takes an even number of parameters. Every odd parameter corresponds to a key type, and every even - to a value type. In your case you should probably use id parameter as a key, so you should call it this way:
cacheConfig.setIndexedTypes(Long.class, Person.class);
Javadoc for setIndexedTypes method contains a pretty good explanation of this method: https://ignite.apache.org/releases/latest/javadoc/org/apache/ignite/configuration/CacheConfiguration.html#setIndexedTypes(java.lang.Class...)
UPD:
There will be registered a table in SQL for each pair of parameters that you provide to setIndexedTypes method.
Your SQL entities will map to cache records and they will have _key and _val columns in addition to the ones that you configured as QuerySqlField-s. So, you should specify types of keys and values that will be used in cache for each table.
You can refer to this page for more information: https://apacheignite.readme.io/docs/dml#basic-configuration
In your case it will be
cacheConfig.setIndexedTypes(KeyType.class, Person.class)
where KeyType is the type you use for keys while calling cache.put(key, person) or insert into Person(_key, ...) ...
Please refer to this documentation section
I am attempting to implement a Hibernate/JPA2 solution over an existing schema, which cannot be changed. Here is a minimal example of the existing schema:
CREATE TABLE REASON (
REASON_CODE CHAR(1),
REASON_DESCRIPTION CHAR(50))
CREATE TABLE HEADER (
REASON_CODE CHAR(1),
OTHERFIELD1 CHAR(40),
OTHERFIELD2 CHAR(40) )
Normally this would be the "correct" way from a DB perspective: Link REASON to HEADER by the REASON_CODE. However it's presenting me with an awkward problem in Java and I'm not sure of the best way to solve it. I've modeled these entities as follows:
#Entity
#Table(name="REASON")
public class Reason implements java.io.Serializable {
#Id
#Column(name="REASON_CODE", unique=true, nullable=false, length=1)
private Character reasonCode;
#Column(name="REASON_DESCRIPTION", nullable=false, length=25)
private String reasonDescription;
}
#Entity
#Table(name="HEADER")
public class Header implements java.io.Serializable {
#ManyToOne
#JoinColumn(name = "REASON_CODE", nullable = false)
private Reason reason;
#Column(name="OTHERFIELD1")
private String otherField1;
#Column(name="OTHERFIELD2")
private String otherField2;
}
Once again, as far as I can tell, this is "correct" from a Java perspective - linking Header to Reason with a reference.
The problem is that when I need to use one of these Reason values in my code I wind up with awkward syntax like:
Reason r = reasonService.findOne('X'); // X is the REASON_CODE in the database record
// Do some processing with variable r
Or this:
header.setReason(reasonService.findOne('X'));
Ideally I could implement Reason as an enum like:
public enum Reason {
X_MARKSTHESPOT("X"),
C_MEANSSOMETHINGELSE("C"),
F_MEANSATHIRDTHING("F") ;
private String code;
private Reason(String code) {
this.code = code;
}
}
And then simply have this in my code:
header.setReason(Reason.X_MARKSTHESPOT);
But from what I understand that is not possible with JPA, which offers only EnumType.STRING (basically the name) or EnumType.ORDINAL (even worse, the index in the enum list). A possible way around this would be JPA 2.1's Converter, but I have never used it. I have also read here (in one of the answers) that a Hibernate User Type might be useful. One of our programmers has solved this in another app by writing two complete classes - an enum class for internal use and a "shadow" class which iterates through the enum and syncs the records in the database on every startup. But this seems like a kludgey way to do it. What is the best way to handle this, bearing in mind that the database schema cannot be changed?
I would like in HQL to use the result of a abstract method in my "where" clause. Can this be done?
Something like this:
#NamedQuery(name="getMailRelations", query="Select mr from MailRelation mr where mr.getOccurrences() > 5"
I have a mapped super class something like this
#Entity
#Table(name="Mail_Entity", schema="relations")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="relationType", discriminatorType=DiscriminatorType.STRING)
#PersistenceContext(name="domas")
public abstract class MailRelation {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST})
#JoinColumn(name="mailId", referencedColumnName="mailId", nullable=false)
private Mail mail;
public void setMail(Mail mail) {
this.mail = mail;
if(!mail.getMailRelations().contains(this))
mail.addMailRelation(this);
}
public abstract int getOccurrences();
public Mail getMail() {
return mail;
// and more code......
No, that is impossible. The HQL code is translated into SQL and executed on the database. Generally Java methods can't be translated into SQL, and the database does not have any access to your Java code.
If you have a problem like this, there are for example these three possibilities to handle it. None of these possibilities is perfect.
1) You write the logic of the method in HQL (or SQL) using WHERE, GROUP BY and HAVING. In your example the getOccurrences() method seems to return a number of rows, which perhaps can be handled by `HAVING COUNT(...) > 5'.
2) You use database stored procedures. These are p. ex. procedures written in PL/SQL (in the case of Oracle). They can be accessed in select statements. But you loose the independency of the chosen database.
3) You load more rows than necessary and filter later in your Java code.
The solution is up to you, but I'm adding some additional options you can consider:
If you manage to precalculate the hash in all cases, use a parametrized named query:
#NamedQuery(name="getMailRelations", query="Select mr from MailRelation mr where :occurrences > 5"
then, you can call the query and add the parameter "occurrences":
String precalculatedHash = //your code here.
entityManager.createNamedQuery("getMailRelations",MailRelation.class).setParameter("occurrences", precalculatedHash).getResultList();
Another option is to go a little deeper with your hash logic, and determine what do you want to achieve with it. With that in mind you can use Criteria API to create a query and add all the restrictions represented by that hash. This can be a little tricky, so discard this option if the hash proves to be too context-depending (and I mean if it relies a lot on what do you have persisted, and the context of your application).
The third option is to bring all the results (or the smallest set of results possible, through either parameters or, again, Criteria API), and make your particular filtering logic.