Is it possible to use raw SQL within a Spring Repository - java

I need to use raw SQL within a Spring Data Repository, is this possible? Everything I see around #Query is always entity based.

The #Query annotation allows to execute native queries by setting the nativeQuery flag to true.
Quote from Spring Data JPA reference docs.
Also, see this section on how to do it with a named native query.

YES, You can do this in bellow ways:
1. By CrudRepository (Projection)
Spring Data Repositories usually return the domain model when using query methods. However, sometimes, you may need to alter the view of that model for various reasons.
Suppose your entity is like this :
import javax.persistence.*;
import java.math.BigDecimal;
#Entity
#Table(name = "USER_INFO_TEST")
public class UserInfoTest {
private int id;
private String name;
private String rollNo;
public UserInfoTest() {
}
public UserInfoTest(int id, String name) {
this.id = id;
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", nullable = false, precision = 0)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Basic
#Column(name = "name", nullable = true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Basic
#Column(name = "roll_no", nullable = true)
public String getRollNo() {
return rollNo;
}
public void setRollNo(String rollNo) {
this.rollNo = rollNo;
}
}
Now your Projection class is like below. It can those fields that you needed.
public interface IUserProjection {
int getId();
String getName();
String getRollNo();
}
And Your Data Access Object(Dao) is like bellow :
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import java.util.ArrayList;
public interface UserInfoTestDao extends CrudRepository<UserInfoTest,Integer> {
#Query(value = "select id,name,roll_no from USER_INFO_TEST where rollNo = ?1", nativeQuery = true)
ArrayList<IUserProjection> findUserUsingRollNo(String rollNo);
}
Now ArrayList<IUserProjection> findUserUsingRollNo(String rollNo) will give you the list of user.
2. Using EntityManager
Suppose your query is "select id,name from users where roll_no = 1001".
Here query will return an object with id and name column. Your Response class is like bellow:
Your Response class is like this:
public class UserObject{
int id;
String name;
String rollNo;
public UserObject(Object[] columns) {
this.id = (columns[0] != null)?((BigDecimal)columns[0]).intValue():0;
this.name = (String) columns[1];
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRollNo() {
return rollNo;
}
public void setRollNo(String rollNo) {
this.rollNo = rollNo;
}
}
here UserObject constructor will get an Object Array and set data with the object.
public UserObject(Object[] columns) {
this.id = (columns[0] != null)?((BigDecimal)columns[0]).intValue():0;
this.name = (String) columns[1];
}
Your query executing function is like bellow :
public UserObject getUserByRoll(EntityManager entityManager,String rollNo) {
String queryStr = "select id,name from users where roll_no = ?1";
try {
Query query = entityManager.createNativeQuery(queryStr);
query.setParameter(1, rollNo);
return new UserObject((Object[]) query.getSingleResult());
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
Here you have to import bellow packages:
import javax.persistence.Query;
import javax.persistence.EntityManager;
Now your main class, you have to call this function. First get EntityManager and call this getUserByRoll(EntityManager entityManager,String rollNo) function. The calling procedure is given below:
Here is the Imports
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
get EntityManager from this way:
#PersistenceContext
private EntityManager entityManager;
UserObject userObject = getUserByRoll(entityManager,"1001");
Now you have data in this userObject.
Note:
query.getSingleResult() return a object array. You have to maintain the column position and data type with the query column position.
select id,name from users where roll_no = 1001
query return a array and it's [0] --> id and [1] -> name.
More info visit this thread and this Thread
Thanks :)

It is possible to use raw query within a Spring Repository.
#Query(value = "SELECT A.IS_MUTUAL_AID FROM planex AS A
INNER JOIN planex_rel AS B ON A.PLANEX_ID=B.PLANEX_ID
WHERE B.GOOD_ID = :goodId",nativeQuery = true)
Boolean mutualAidFlag(#Param("goodId")Integer goodId);

we can use createNativeQuery("Here Native SQL Query ");
for Example :
Query q = em.createNativeQuery("SELECT a.firstname, a.lastname FROM Author a");
List<Object[]> authors = q.getResultList();

This is how you can use in simple form
#RestController
public class PlaceAPIController {
#Autowired
private EntityManager entityManager;
#RequestMapping(value = "/api/places", method = RequestMethod.GET)
public List<Place> getPlaces() {
List<Place> results = entityManager.createNativeQuery("SELECT * FROM places p limit 10").getResultList();
return results;
}
}

It is also possible to use Spring Data JDBC, which is a fully supported Spring project built on top of Spring Data Commons to access to databases with raw SQL, without using JPA.
It is less powerful than Spring Data JPA, but if you want lightweight solution for simple projects without using a an ORM like Hibernate, that a solution worth to try.

Related

Hibernate 6 regression with generics

I tried to migrate my application from Hibernate 5.4.30.Final to 6.1.6.Final, database H2 2.1.214. I observed a different behaviour regarding generics when using a CriteriaQuery. I have stripped it down to a testcase (which does not make any sense but shows the problem). In Hibernate 5 the following query to a generic field name runs fine whereas Hibernate 6 throws an Exception.
CriteriaBuilder cb = eMgr.getCriteriaBuilder();
CriteriaQuery<String> cr = cb.createQuery(String.class);
Root<Person> person = cr.from(Person.class);
cr.select(person.<String> get("name"));
TypedQuery<String> query = eMgr.createQuery(cr);
Exception:
Converting `org.hibernate.query.QueryTypeMismatchException` to JPA `PersistenceException` : Specified result type [java.lang.String] did not match Query selection type [java.lang.Object] - multiple selections: use Tuple or array
Here are my sample class definitions:
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
#Entity
public class GenericPerson<T>
{
#Id
#GeneratedValue(generator = "increment")
private long id;
private T name;
public GenericPerson() { }
public GenericPerson(T name) { this.name = name;}
public T getName() { return this.name; }
public void setName(T name) { this.name = name; }
public long getId() { return this.id;}
public void setId(long id) { this.id = id; }
}
#Entity
public class Person extends GenericPerson<String>
{
public Person() { }
public Person(String name) { super(name); }
}
Hibernate 5 seems to handle generics differently to Hibernate 6 but I could not find any hint in the migration document. Why fails the test case with Hibernate 6?
The solution is to use the cast method for the generic field:
cr.select(person.get("name").as(String.class));

JPA createQuery where condition does not work

I am trying to use JPA to fetch records from database. However I am able to insert records indatabse and even get all the records using createQuery method of class EntityManager.
But in below case I am not getting why the condition in where clause is not working.
Please help me figure it out.
POJO class :
#Entity
#Table(name = "frameworks_filter")
public class FilteredFrameworksDbStructure {
#Id
#Column(name="id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "regular_name")
private String regularName;
#Column(name = "component_name")
private String componentName;
#Column(name = "component_owner")
private String componentOwner;
#Column(name = "frameworks")
private String frameworks;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getRegularName() {
return regularName;
}
public void setRegularName(String regularName) {
this.regularName = regularName;
}
public String getComponentName() {
return componentName;
}
public void setComponentName(String componentName) {
this.componentName = componentName;
}
public String getComponentOwner() {
return componentOwner;
}
public void setComponentOwner(String componentOwner) {
this.componentOwner = componentOwner;
}
public String getFrameworks() {
return frameworks;
}
public void setFrameworks(String frameworks) {
this.frameworks = frameworks;
}
}
DAO class method:
public List<FilteredFrameworksDbStructure> getFilteredFrameworks(String regularName) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
List<FilteredFrameworksDbStructure> filteredFrameworksDbStructureList = entityManager
.createQuery("from FilteredFrameworksDbStructure F where F.regularName = :regular", FilteredFrameworksDbStructure.class)
.setParameter("regular", regularName)
.getResultList();
return filteredFrameworksDbStructureList;
}
Issue : Condition in where clause does not work. It simply fetch all the records irrespective of the regularName provided.
Regards,
Parag Vinchurkar
Why don't you use the JpaRepository or CrudRepository to fetch your results? Check out this tutorial here and here on how to use them.
And you can use your where clause. Please see below the example repository you can use to obtain the same results as the entityManager
public interface FilteredFrameworksDbStructureRepo extends JpaRepository<FilteredFrameworksDbStructure , Integer>{
List<FilteredFrameworksDbStructure> findAllByRegularName(String regularName)
}
Please note that you will have to change your id member variable from int to Integer

add data to database (related tables) Spring Boot

I know English badly, but i'm trying to describe my problem.
I'm new in Spring. And I have some problems with adding data to my database.I have to table Pc and Pc characteristics. They are related by Id. It's easy to add data in non realted table, but how can I add data in related table? What shoud I write in my Controller? There are some classes below.
Pc class:
#Entity
#Table(name = "pc")
public class Pc {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private int price;
public Pc(){}
public Pc(String name, int price) {
this.name = name;
this.price = price;
}
#OneToMany
#JoinColumn(name = "pc_id")
private List<PcChars> chars = new ArrayList<>();
public List<PcChars> getChars() {
return chars;
}
public void setChars(List<PcChars> chars) {
this.chars = chars;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
PcChars class:
#Entity
#Table(name = "pcChars")
public class PcChars {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private String value;
public PcChars(){}
public PcChars(String name, String value) {
this.name = name;
this.value = value;
}
#ManyToOne
private Pc pc;
public Pc getPc() {
return pc;
}
public void setPc(Pc pc) {
this.pc = pc;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
PcCharactsController:
#Controller
public class PcCharactsController {
final private PcRepo pcRepo;
final private PcCharRepo pcCharRepo;
public PcCharactsController(PcRepo pcRepo, PcCharRepo pcCharRepo) {
this.pcRepo = pcRepo;
this.pcCharRepo = pcCharRepo;
}
//Pc characteristics list
#GetMapping("pc/{id}/")
public String pcCharList(#PathVariable int id, Model model) throws Exception{
Pc pc = pcRepo.findById(id).orElseThrow(() -> new Exception("PostId " +
id + " not found"));
List<PcChars> pcChars = pc.getChars();
model.addAttribute("model", pc.getName());
model.addAttribute("pcChars", pcChars);
return "charList";
}
//add characteristic
#PostMapping("pc/{id}/")
public String addCharact(){
return "charList";
}
Characteristics.ftl:
<html>
<head>
<title>Ho</title>
</head>
<body>
<div>
<form method="post" action="/pc/${id}/">
<input type="text" name="name">
<input type="text" value="value">
<input type="hidden" name="pc_id" value="${id}">
<button type="submit">Add</button>
</form>
</div>
</body>
</html>
Since you are not using any modelAttribute to bind the input values straight to a POJO you can use simple HttpServletRequest to get the input attributes, use them to create the object you want to store and store it using Hibernate
#PostMapping("pc/{id}/")
public String addCharact(HttpServletRequest req){
String name = req.getParameter("name");
String value = req.getParameter("value");
String id = req.getParameter("id");
PcChars pcchars = new PcChars(name,value,id); // create the corresponding constructor
SessionFactory sessionFactory;
Session session = sessionFactory.openSession();
Transaction tx = null;
try{
tx = session.getTransaction();
tx.begin();
session.save(pcchars);
tx.commit();
}
catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
} finally {
session.close();
}
return "charList";
}
The part of Spring you're using is called Spring data, a library that allows you to use JPA in your Spring application. JPA is a specification for frameworks called ORM (Object-relationnal mapping).
To keep it simple, in your code, you do not use the relational approach anymore, but an object approach. Annotations you put on your classes' fields are used to define mappings between them and your database tables and fields.
So, you don't have to insert both entities separately anymore. You need to create a Pc instance, then to create a PcChars one, and finally to add the chars into the pc's chars list, like this :
Pc myPc = new Pc();
PcChars myChars = new PcChars();
myPc.getChars().add(myChars);
And when you'll use your repository to save the modifications with this :
pcRepo.save(myPc);
The JPA implementation will automatically do the work for you :
Inserting the row corresponding to your PC instance in the PC table
Inserting the row corresponding to your PC chars in the the PC_CHARS table
Settings the PC_CHARS.PC_ID with the ID of the freshly inserted PC instance's id in order to create the reference between them.
Not sure, but I think the ORM also do this when you add your chars to the pc instance :
myChars.setPc(myPc);
in order to make the bound between both instances reciprocal.
Note that I used arbitrary field names according to your schema.
I strongly suggest you to give responsibility of relationship to child side when using #OneToMany relation.
Modify your parent class as below:
#OneToMany(cascade = CascadeType.ALL, mappedBy="pc")
#BatchSize(size = 10)
private List<PcChars> chars = new ArrayList<>();
public void addPcChar(PcChar pcChar) {
this.chars.add(pcChar);
pcChar.setPc(this);
}
On the child class:
#ManyToOne
#JoinColumn(name = "pc_id")
private Pc pc;
Now you can persist your parent with child as below :
Pc pc = new Pc();
PcChar pcChar = new PcChar();
pc.addPcChar(pcChar);
If you use spring boot data repository, it saves it correctly as below
// assume your repository like below
public interface PcRepository extends CrudRepository<Pc, Integer> {}
// in your service or whatever in some place
pcRepository.save(pc);
With saving hibernate entity manager:
EntityManagerFactory emfactory =
Persistence.createEntityManagerFactory("Hibernate");
EntityManager entitymanager = emfactory.createEntityManager();
entitymanager.getTransaction().begin();
entitymanager.persist(pc);
entitymanager.getTransaction().commit();
entitymanager.close();
emfactory.close();
For detailed information about hibernate relationship take a look at my post : https://medium.com/#mstrYoda/hibernate-deep-dive-relations-lazy-loading-n-1-problem-common-mistakes-aff1fa390446

Using SQL-IN-clause in custom #Query in JPA-/CrudRepository with the list or set as passed parameter?

Hi Spring and Hibernate experts!
Can any one say if it is possible to use SQL IN-clause in custom #Query in CrudRepository while the Arraylist or set of strings is passed as parameter?
I am relatively new to Spring and do not quite figure out why I get the following Spring error:
"java.lang.IllegalArgumentException: Parameter value [d9a873ed-3f15-4af5-ab1b-9486017e5611] did not match expected type [IoTlite.model.Device (n/a)]"
In this post (JPQL IN clause: Java-Arrays (or Lists, Sets...)?) the subject is discussed pretty closely but I cannot make the suggested solution to work in my case with custom #Query.
My demo repository as part of the spring boot restful application is the following:
#Repository
public interface DeviceRepository extends JpaRepository<Device, Long> {
#Query("SELECT d FROM Device d WHERE d IN (:uuid)")
List<Device> fetchUuids(#Param("uuid") Set<String> uuid);
}
And the model-class is the following:
#Entity
#SequenceGenerator(sequenceName = "device_seq", name = "device_seq_gen", allocationSize = 1)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Device implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "device_seq_gen")
#JsonIgnore
private Integer id;
#Column(unique=true, length=36)
#NotNull
private String uuid = UUID.randomUUID().toString();
#Column(name="name")
private String name;
#JsonInclude(JsonInclude.Include.NON_NULL)
private String description;
#OneToMany(
mappedBy="device",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Sensor> sensors = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#JsonIgnore
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDeviceUuid() {
return uuid;
}
public void setDeviceUuid(String deviceUuid) {
this.uuid = deviceUuid;
}
public List<Sensor> getSensors() {
return sensors;
}
public void addSensor(Sensor sensor){
sensor.setDevice(this);
sensors.add(sensor);
}
}
An here is the relevant part of the service calling the fetchUuids-custom-method with set-list of strings as parameter (service naturally being called by the relevant restcontroller):
#Service
public class DeviceService implements IDeviceService {
#Autowired
private DeviceRepository deviceRepository;
...
#Override
public List<Device> listDevices(Set<String> clientIds) {
return deviceRepository.fetchUuids(clientIds);
}
...
}
Quick fix
You have WHERE d IN (:uuid) in the custom query. You cannot match d, which is an alias for Device entity with :uuid parameter, which is a collection of Strings.
WHERE d.uuid IN (:uuid) would fix the query - it matches a String with Strings.
What you should do instead
It's rather misleading to name the method fetchUuids and return a list of Device instances. It's also unnecessary to write a custom query to do that. You can benefor from repository method name conventions and let Spring Data Jpa framework generate the query for you:
List<Device> findByUuidIn(Set<String> uuids);
You can write in this way
#Query(value = "select name from teams where name in :names", nativeQuery = true)
List<String> getNames(#Param("names") String[] names);
and call the function in service and pass an array of String as arguments.like this
String[] names = {"testing team","development team"};
List<String> teamtest = teamRepository.getNames(names);
Yes is possible to using collection in JPA query parameters.
Your query is wrong, it should be like this:
#Query("SELECT d FROM Device d WHERE d.uuid IN :uuid")

How to handle join query in Hibernate and Spring with annotations?

I am developing application using Spring and Hibernate with MySQL. I am new to Hibernate and did basic tasks...
Now I need to apply joins in select query to get data from multiple table using annotations. I have searched for it but still I didn't get any idea...
Here my database tables and bean classes :
Table 1: 'employee_info' ( id, empid, empname, doj and jobtitle )
Table 2: 'employee_login' ( username, password, status and empid )
And my bean classes are:
EmployeeInfoForm.java
#Entity()
#Table(name = "employee_info")
public class EmployeeInfoForm {
#Id
#GeneratedValue
#Column(name = "id", unique = true, nullable = true)
private int id;
#Column(name = "empId")
private int empId;
#Column(name = "empname")
private String empName;
#Column(name = "doj")
private Date empDoj;
#Column(name = "jobtitle")
private String empJobTitle;
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public void setEmpDoj(Date empDoj) {
this.empDoj = empDoj;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public Date getEmpDoj() {
return empDoj;
}
public void setEmp_Doj(Date empDoj) {
this.empDoj = empDoj;
}
public String getEmpJobTitle() {
return empJobTitle;
}
public void setEmpJobTitle(String empJobTitle) {
this.empJobTitle = empJobTitle;
}
}
EmployeeLoginForm.java
#Entity()
#Table(name = "employee_login")
public class EmployeeLoginForm {
#Id
#Column(name = "username")
private String empUserName;
#Column(name = "password")
private String empPassword;
#Column(name = "status")
private String empStatus;
#Column(name = "empid")
private int empId;
public String getEmpUserName() {
return empUserName;
}
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public void setEmpUserName(String empUserName) {
this.empUserName = empUserName;
}
public String getEmpPassword() {
return empPassword;
}
public void setEmpPassword(String empPassword) {
this.empPassword = empPassword;
}
public String getEmpStatus() {
return empStatus;
}
public void setEmpStatus(String empStatus) {
this.empStatus = empStatus;
}
}
Requirement:
I want to select fields empid, empname, jobtitle from employee_info and field status from employee_login table when the empid matches on both table...
Please help me to complete my work...
Any suggestions and guidance are appreciated...
There is an association between EmployeeInfoForm and EmployeeLoginForm that I am not seeing in your code. Maybe there is an Employee class there? If that is the case then you need to add that. So let us assume that each employee has many forms. Then you will code the Employee side of the relationship like this:
public class Employee{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "employee")
private Set<EmployeeLoginForm> loginForms = new HashSet<EmployeeLoginForm>();
...
}
And the Many side of the relationship in the EmployeeLoginForm class:
#ManyToOne
Employee employee;
This will create the table structure such that:
emploee = (id, etc ...)
employeelogin = (id, employee, ....)
Now, any time you need a list of the Logins of an Employee you get it from the Employee object without needing a Query.
Set<EmployeeLoginForm> logins = e.getLoginForms(); //where e is an employee object.
If you did want to query you can do
select o from EmployeeLoginForm o join o.employee
But that is unnecessary in this case.
You are thinking in database / pure SQL terms when you talk about performing joins with select statements. The power (and danger) of Hibernate is that it abstracts this away from you and lets you think in Object terms. What you need is a relationship between the 2 objects and then let Hibernate handle this relationship.
I recommend you spend some time reading this:
http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/associations.html
to get a better understanding of how Hibernate can help.
You can do the following using the Hibernate criteria projection:
public List extractEmployeeAttributes() {
log.debug("extractEmployeeAttributes");
try {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Criteria c1 = session.createCriteria(employee_info.class,emp_info);
Criteria c2 = session.createCriteria(employee_login.class,emp_log);
c1.setProjection(Projections.projectionList()
.add(Projections.property("empid"))
.add(Projections.property("empname"))
.add(Projections.property("jobtitle"))
.add(Projections.property("employee_info "))
.add(Restrictions.and(Property.eqName(emp_info.empId,emp_log.empId))
return c1.list();
} catch (RuntimeException re) {
log.error("extractEmployeeAttributes failed", re);
throw re;
}
}

Categories

Resources