Call database function with Spring - java

I need to query a function of the database, by SQL, something so simple as
#Query("SELECT random()")
how to implement a domain/repository or service method that do it?

There are various ways how to bind stored procedures with Spring Data JPA (using JPA 2.1).
Simplest example:
#Procedure("random")
Integer randomStoredProcedure();

In case, you don't have any Entity/DTO (non #Entity) objects and wondering how to map returned table from the function into your custom object.
#Repository
public class YourRepository {
private final EntityManager entityManager;
public(EntityManager entityManager) {
this.entityManager = entityManager;
}
public List<YourObject> findObjects() {
List<Object[]> columns = entityManager
.createStoredProcedureQuery("your_sql_func")
.getResultList();
return columns.stream().map(this::toYourObject).collect(Collectors.toList());
}
private YourObject toYourObject(Object[] columns) {
return new YourObject((int)columns[0], (String)columns[1]);
}
}
class YourObject {
private int number;
private String name;
public YourObject(int number, String name) {
this.number = number;
this.name = name;
}
// getters/setters or use Lombok
}
If you have any parameters in your function then your query should look like this,
List<Object[]> columns = entityManager
.createStoredProcedureQuery("your_sql_func")
.registerStoredProcedureParameter("param1Name", String.class, ParameterMode.IN)
.setParameter("param1Name", param1Value)
.getResultList();
You could use native query as well,
List<Object[]> columns = entityManager
.createNativeQuery("SELECT * FROM your_sql_func(:param1Name)")
.setParameter("param1Name", param1Value)
.getResultList();

Related

when query at JPA - JPQL

My system required to add filters,and I'm wonder if there any query that like this
SELECT *
FROM posts p
when byDate is not null then (where p.createAt BETWEEN :startDate AND :endDate)
when byType is not null then (where p.type = :type)
I knew that the query is not valid, but I want at one query to get the data wherever the request has (no filter or all filters or some of filters).
My goal is to create one query to achieve all cases.
It's usually not a good idea to write a big SQL query when you can tell in advance the actual query you want to run.
If you want to run a different query based on conditions you know before running the query, there are different approaches in JPA or Spring that you can use
Spring
You can define the different queries using Spring Data query methods?
public class PostRepository implements JpaRepository<Post, Long> {
List<Post> findByCreatedAtBetween(Date startDate, Date endDate);
List<Post> findByTypeIs(String type);
}
And then somewhere in the code, you can:
List<Post> results = null;
if (byDate != null) {
results = repository.findByCreatedAtBetween(startDate, endDate);
} else if (byType != null) {
results = repository.findByTypeIs(type);
} else {
results = repository.findAll();
}
Criteria
With criteria you can create a dynamic query at runtime and execute it:
public class PostRepository implements PostRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Post> findPosts(Filter filter) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(Post.class);
Root<User> user = query.from(Post.class);
if ((filter.getByDate() != null)) {
// byDate is not null
ParameterExpression<Date> startDate = builder.parameter( Date.class );
ParameterExpression<Date> endDate = builder.parameter( Date.class );
query.where(builder.between( b.get( "createdAt" ), startDate, endDate));
return em.createQuery(query)
.setParameter(startDate, ...)
.setParameter(endDate, ...)
.getResultList();
}
if (filter.getByType() != null) {
ParameterExpression<Date> typeParam = builder.parameter( Date.class );
query.where(builder.and(root.get("type"), typeParam));
return em.createQuery(query)
.setParameter(typeParam, ...)
.getResultList();
}
return entityManager.createQuery(query)
.getResultList();
}
}
Assuming that your entity has the fields type and createdAt.
This approach works well if you don't know in advance what's your query looks like. For example, when you don't know how many conditions you will have to add to it.
But, if I know already which query I want to run, then I prefer to use HQL/JPQL.
HQL
If your queries don't change and you already know what they look like,
I find it easier to define them with HQL:
public class PostRepository implements PostRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Post> findPosts(Filter filter) {
if (filter.getByDate() != null) {
return em.createQuery("from Post p where p.createdAt between :startDate and :endDate", Post.class)
.setParameter("startDate", ...)
.setParameter("endDate", ...)
.getResultList();
}
if (filter.getByType() != null) {
return em.createQuery("from Post p where p.type =:type", Post.class)
.setParameter("type", ...)
.getResultList();
}
return em.createQuery("from Post", Post.class)
.getResultList();
}
}
You can refactor the code to make it more elegant, but it should give you an idea. Note that if you need to reuse the same queries in different services, it might be helpful to define them using the annotation #NamedQuery.
Filters
In Hibernate (not JPA) you can also define filters. They are SQL filter conditions that one can apply at runtime:
#Entity
#FilterDef(name = Post.BY_DATE, defaultCondition = "createdAt between :startDate and :endDate", parameters = {#ParamDef(name = "startDate", type = "date"), #ParamDef(name = "startDate", type = "date") })
#FilterDef(name = Post.BY_TYPE, defaultCondition = "type = :type", parameters = #ParamDef(name = "startDate", type = "date"))
#Filter(name = Post.BY_DATE)
#Filter(name = Post.BY_TYPE)
class Post {
static final String BY_DATE = "Post.byDateFilter";
static final String BY_TYPE = "Post.byFilter"
private String type;
private Date createdAt;
...
}
Then:
public class PostRepository implements PostRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Post> findPosts(Filter filter) {
enableFilters(em);
return em.createQuery("from Post", Post.class).getResultList();
}
private void enableFilters(Filter filter, EntityManager em) {
if (filter.getByDate() != null) {
em.unwrap(Session.class)
.enableFilter( Post.BY_DATE )
.setParameter("startDate", ...)
.setParameter("endDate", ...);
} else if (filter.getByType() != null) {
em.unwrap(Session.class)
.enableFilter( Post.BY_TYPE )
.setParameter("type", ...);
}
}
}

Invoking a Stored procedure in spring boot application and assign the result to RowMapper

I have a stored procedure which returns a List. I need to invoke it and assign the result to a model call.
Below is the Repository class i have written
#Repository
public class addRepository {
#Procedure(name = "up_Getdata(?)"
public List<String> fetchResult(#Param("inParam") String inParam){
}
My model class has 3 fields name,age,departName
Any help on how to map this will he helpful
I think the best answer would be to simply follow this tutorial on Baeldung, the tl;dr would be:
Create your entity class with the 3 fields.
Change the return type of fetchResult() to your newly created entity.
Change addRepository from a class to an interface
Be sure that you chose the correct procedure name, you can choose between one of the following ways
#Procedure("up_Getdata")
#Procedure(procedureName = "up_Getdata")
#Procedure(value = "up_Getdata")
I am not sure if #Procedure(name = "up_Getdata) would be valid
Another approach without #Procedure but works perfectly also in spring boot:
#Repository
public class YourRepository {
#PersistenceContext
private EntityManager entityManager;
public List<YourModelClass> executeStoredProcedure(String inParam) {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery(
"up_Getdata", YourMapper.MAPPING_NAME);
query.registerStoredProcedureParameter("#inParam", String.class, ParameterMode.IN);
query.setParameter("#inParam", inParam);
return query.getResultList();
}
}
Your Mapper:
#MappedSuperclass
#SqlResultSetMapping(name = YourMapper.MAPPING_NAME, classes = {
#ConstructorResult(targetClass = YourModelClass.class, columns = { #ColumnResult(name = "name"),
#ColumnResult(name = "age"), #ColumnResult(name = "departName") }) })
public abstract class YourMapper {
public static final String MAPPING_NAME = "MappingName";
}

How to fix missing descriptor for class POJO after update server? [duplicate]

I'm using EclipseLink to run some Native SQL. I need to return the data into a POJO. I followed the instructions at EclipseLink Docs, but I receive the error Missing descriptor for [Class]
The query columns have been named to match the member variables of the POJO. Do I need to do some additional mapping?
POJO:
public class AnnouncementRecipientsFlattenedDTO {
private BigDecimal announcementId;
private String recipientAddress;
private String type;
public AnnouncementRecipientsFlattenedDTO() {
super();
}
public AnnouncementRecipientsFlattenedDTO(BigDecimal announcementId, String recipientAddress, String type) {
super();
this.announcementId = announcementId;
this.recipientAddress = recipientAddress;
this.type = type;
}
... Getters/Setters
Entity Manager call:
public List<AnnouncementRecipientsFlattenedDTO> getNormalizedRecipientsForAnnouncement(int announcementId) {
Query query = em.createNamedQuery(AnnouncementDeliveryLog.FIND_NORMALIZED_RECIPIENTS_FOR_ANNOUNCEMENT, AnnouncementRecipientsFlattenedDTO.class);
query.setParameter(1, announcementId);
return query.getResultList();
}
I found out you can put the results of a Native Query execution into a List of Arrays that hold Objects. Then one can iterate over the list and Array elements and build the desired Entity objects.
List<Object[]> rawResultList;
Query query =
em.createNamedQuery(AnnouncementDeliveryLog.FIND_NORMALIZED_RECIPIENTS_FOR_ANNOUNCEMENT);
rawResultList = query.getResultList();
for (Object[] resultElement : rawResultList) {
AnnouncementDeliveryLog adl = new AnnouncementDeliveryLog(getAnnouncementById(announcementId), (String)resultElement[1], (String)resultElement[2], "TO_SEND");
persistAnnouncementDeliveryLog(adl);
}
You can only use native SQL queries with a class if the class is mapped. You need to define the AnnouncementRecipientsFlattenedDTO class as an #Entity.
Otherwise just create the native query with only the SQL and get an array of the data back and construct your DTO yourself using the data.
Old question but may be following solution will help someone else.
Suppose you want to return a list of columns, data type and data length for a given table in Oracle. I have written below a native sample query for this:
private static final String TABLE_COLUMNS = "select utc.COLUMN_NAME, utc.DATA_TYPE, utc.DATA_LENGTH "
+ "from user_tab_columns utc "
+ "where utc.table_name = ? "
+ "order by utc.column_name asc";
Now the requirement is to construct a list of POJO from the result of above query.
Define TableColumn entity class as below:
#Entity
public class TableColumn implements Serializable {
#Id
#Column(name = "COLUMN_NAME")
private String columnName;
#Column(name = "DATA_TYPE")
private String dataType;
#Column(name = "DATA_LENGTH")
private int dataLength;
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public int getDataLength() {
return dataLength;
}
public void setDataLength(int dataLength) {
this.dataLength = dataLength;
}
public TableColumn(String columnName, String dataType, int dataLength) {
this.columnName = columnName;
this.dataType = dataType;
this.dataLength = dataLength;
}
public TableColumn(String columnName) {
this.columnName = columnName;
}
public TableColumn() {
}
#Override
public int hashCode() {
int hash = 0;
hash += (columnName != null ? columnName.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof TableColumn)) {
return false;
}
TableColumn other = (TableColumn) object;
if ((this.columnName == null && other.columnName != null) || (this.columnName != null && !this.columnName.equals(other.columnName))) {
return false;
}
return true;
}
#Override
public String toString() {
return getColumnName();
}
}
Now we are ready to construct a list of POJO. Use the sample code below to construct get your result as List of POJOs.
public List<TableColumn> findTableColumns(String table) {
List<TableColumn> listTables = new ArrayList<>();
EntityManager em = emf.createEntityManager();
Query q = em.createNativeQuery(TABLE_COLUMNS, TableColumn.class).setParameter(1, table);
listTables = q.getResultList();
em.close();
return listTables;
}
Also, don't forget to add in your POJO class in persistence.xml! It can be easy to overlook if you are used to your IDE managing that file for you.
Had the same kind of problem where I wanted to return a List of POJOs, and really just POJOs (call it DTO if you want) and not #Entity annotated Objects.
class PojoExample {
String name;
#Enumerated(EnumType.STRING)
SomeEnum type;
public PojoExample(String name, SomeEnum type) {
this.name = name;
this.type = type;
}
}
With the following Query:
String query = "SELECT b.name, a.newtype as type FROM tablea a, tableb b where a.tableb_id = b_id";
Query query = getEntityManager().createNativeQuery(query, "PojoExample");
#SuppressWarnings("unchecked")
List<PojoExample> data = query.getResultList();
Creates the PojoExample from the database without the need for an Entity annotation on PojoExample. You can find the method call in the Oracle Docs here.
edit:
As it turns out you have to use #SqlResultSetMapping for this to work, otherwise your query.getResultList() returns a List of Object.
#SqlResultSetMapping(name = "PojoExample",
classes = #ConstructorResult(columns = {
#ColumnResult(name = "name", type = String.class),
#ColumnResult(name = "type", type = String.class)
},
targetClass = PojoExample.class)
)
Just put this anywhere under your #Entity annotation (so in this example either in tablea or tableb because PojoExample has no #Entity annotation)

Map Custom JdbcTemplate query result in an Object

I new in java and try to use spring framework. I have a question.
By example, I have table :
employee (id_employee, name)
employee_product (id_employee_product, id_employee, product_name)
if I select an employee data from my Employee table, I can map it in a POJO model User and define the tables structure in that model, like this:
public class Employee {
private final int id_employee;
private final String nama;
public Employee(int id_employee, String nama){
this.id_employee = id_employee;
this.nama = nama;
}
public int getId() {
return id_employee;
}
public String getNama() {
return nama;
}
}
And this is the map from jdbcTemplate:
final String sql = "SELECT id_employee, nama FROM employee";
return jdbcTemplate.query(sql, (resultSet, i) -> {
return new Employee(
resultSet.getInt("id_employee"),
resultSet.getString("nama")
);
});
That is clear example for select data from 1 table.
My question is, how to map data from query if my data is custom query? Such us using join and select custom field from that tables, Am I need to create POJO every query?
Sometimes I need to select only employee.id_employee, and employee.name field from my employee table.
And in another controller I need to select employee.id_employee from my employee table.
In another case, I need only select employee.name, and employee_product.product_name
Is there an alternative to map the data without creating POJO for every case?
Create a one POJO combining two tables like this
public class Employee {
private int id_employee;
private String name;
private int id_employee_product.
private String product_name
//getter and setters
//Don't create a constructor its Entiry
}
Now by using a BeanPropertyRowMapper Doc Link write your repository like
public List<Employee> fetchEmployeeProduct(){
JdbcTemplate jdbcTemplate = new JdbcTemplate("Your_DataSource");
StringBuilder query = new StringBuilder();
query.append("Your Query");
List<Employee> employeeProductList =
jdbcTemplate.query(query.toString(), new BeanPropertyRowMapper<Employee>(Employee.class));
}
Make sure SELECT clause in the query and Employee POJO's filed name is same.
Once if you execute your query it will automatically map to POJO. You no need to write a custom mapper BeanPropertyRowMapperwill take care of mapping.

Variables in Spring Data JPA native query

Using Spring Dat JPA, I need to query my database and return a range of OrderEntitys based on a startAmt and a endAmt of amounts. I'm not sure if I should map these two variables to entity OrderEntity, as fields in some type of separate class/entity/model, or simply declare them in my native query. Perhaps I should be using a service that implements EntityManager.createNativeQuery()?
Would like to do something like :
#Repository
public interface OrderRangeRepository extends JpaRepository<OrderEntity, OrderEntityID> {
#Query(value = "SELECT * FROM Orders WHERE Amount BETWEEN startAmt AND endAmt;" , nativeQuery=true)
List<OrderEntity> findOrdersBy(int startAmt, int endAmt);
}
If I were to use EntityManager.createNativeQuery() in a service, perhaps something like below :
#Service
public class OrderRangeService {
#Autowired
EntityManager entityManager;
public List<OrderEntity> findAmountsBetween() {
List<OrderEntity> amountsBetween = entityManager.createNativeQuery("SELECT * FROM Orders WHERE Amount BETWEEN ?1 AND 2?;")
.setParameter(1, "startAmt")
.setParameter(2, "endAmt")
.getResultList();
return amountsBetween;
}
}
You can achieve this with Spring Data JPA without defining a native query.
#Repository
public interface OrderRangeRepository extends JpaRepository<OrderEntity, OrderEntityID> {
List<OrderEntity> findByAmountBetween(int startAmt, int endAmt);
}
If you want to use the native query change it to
#Query(value = "SELECT * FROM Orders WHERE Amount BETWEEN :startAmt AND :endAmt" , nativeQuery=true)
List<OrderEntity> findOrdersBy(#Param("startAmt") int startAmt, #Param("endAmt") int endAmt);
You can invoke the query in a service by doing
#Service
public class OrderRangeService {
#Autowired
OrderRangeRepository orderRangeRepository ;
public List<OrderEntity> findAmountsBetween(int startAmt, int endAmt) {
List<OrderEntity> amountsBetween = orderRangeRepository.findByAmountBetween(startAmt, endAmt);
return amountsBetween;
}
}
Finally, from your controller, you should autowire the OrderRangeService and invoke the findAmountsBetween service method
#Autowired
OrderRangeService orderRangeService;
#GetMapping("/amountsFromAndTo")
#ResponseBody
public String getAmounts(#RequestParam int startAmt, #RequestParam int endAmt) {
List<OrderEntity> orderEntityL = orderRangeService.findAmountsBetween(startAmt, endAmt);
return orderEntityL.toString();
}
1. Named Parameters
Each parameter annotated with #Param must have a value string matching
the corresponding JPQL or SQL query parameter name. A query with named
parameters is easier to read and is less error-prone in case the query
needs to be refactored.
#Query(value = "SELECT * FROM Orders WHERE Amount BETWEEN :startAmt AND :endAmt;" , nativeQuery=true)
List<OrderEntity> findOrdersBy(#Param("startAmt") int startAmt, #Param("endAmt") int endAmt);
}
2. Indexed Query Parameters
Spring Data will pass method parameters to the query in the same order
they appear in the method declaration
#Query(value = "SELECT * FROM Orders WHERE Amount BETWEEN ?1 AND ?2;" , nativeQuery=true)
List<OrderEntity> findOrdersBy(int startAmt, int endAmt);

Categories