I have an entity, and the DAO with interface JpaRepository<MyEntity, Long>. Using EclipseLink.
I'm using the following method from the DAO:
Iterable<MyEntity> findAll(Iterable<Long> ids);
in this way:
List<Long> listOfIds = Arrays.asList(new Long[] {1,2,3});
Iterable<MyEntity> entities = dao.findAll(listOfIds);
I've got the MySQL exception:
java.sql.SQLException: Operand should contain 1 column(s)
The SQL query that is executed in the database has the following syntax:
SELECT id, creation_date, column1, column2 FROM my_entity WHERE (id IN ((1,2,3)))
The problem is in the last braces - there are too many of them. The working query is:
SELECT id, creation_date, column1, column2 FROM my_entity WHERE (id IN (1,2,3))
Any reason why the Spring Data adds these unnecessary braces? Any way to fix it?
FOUND WORKAROUND
First of all, your DAO must also implement the JpaSpecificationExecutor<MyEntity>.
Then, create a Specification-factory class like this:
public final class MyEntitySpecifications {
public static Specification<MyEntity> idsIn(final Collection<Long> ids) {
return new Specification<MyEntity>() {
#Override
public Predicate toPredicate(Root<MyEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return root.get("id").in(ids);
}
};
}
}
and use your DAO like this:
Iterable<MyEntity> entities = dao.findAll(MyEntitySpecifications.idsIn(listOfIds));
Produced query is now as expected.
Related
I want to run some native queries and expose the results through endpoints, but I want to do this without having to create all the entities. I just want the data obtained from the database to be exposed as it comes.
I found some suggestions at: Create spring repository without entity
However, I was not able to make them work. I'm very new to Spring.
I tried Maciej Kowalski's solution like this:
Interface:
public interface CustomNativeRepository {
Object runNativeQuery();
}
Implementation:
#Repository
public class CustomNativeRepositoryImpl implements CustomNativeRepository {
#Autowired
private EntityManager entityManager;
#Override
public Object runNativeQuery() {
return entityManager.createNativeQuery(
"""
SELECT 1 as col1, 2 as col2, 3 as col3
UNION ALL SELECT 4, 5, 6
UNION ALL SELECT 7, 8, 9
"""
)
.getResultList();
}
}
However, no endpoints were exposed, as happens when you extend CrudRepository. Should I have done something else with CustomNativeRepositoryImpl? I don't know how to proceed.
I also tried Gagarwa's solution:
RootEntity:
#Entity
public class RootEntity {
#Id
private Integer id;
}
RootEntityRepository:
#Repository
public interface RootEntityRepository extends JpaRepository<RootEntity, Integer> {
#Query(value = """
SELECT 1 as col1, 2 as col2, 3 as col3
UNION ALL SELECT 4, 5, 6
UNION ALL SELECT 7, 8, 9""",
nativeQuery = true)
public Collection<Object> findFromCustomQuery();
}
The endpoint http://localhost:8080/rootEntities was exposed, but when I accessed it, I got the exception: "Relation root_entity does not exist". So, I created the table in the database:
create table root_entity(
id SERIAL PRIMARY KEY
)
After that, the endpoint worked, and returned an empty array (the table root_entity is empty in the database).
I tried to access the endpoint: http://localhost:8080/rootEntities/search/findFromCustomQuery, but I got an exception (Couldn't find PersistentEntity for type class).
Again, I was not able to make it work.
After trying a lot, I made some progress doing the following:
#RestController
public class CustomQueryController {
#Autowired
private EntityManager entityManager;
#GetMapping("/myEndpoint")
#ResponseBody
public Object runNativeQuery() {
return ResponseEntity
.ok()
.body(
entityManager.createNativeQuery(
"""
SELECT 1 as col1, 2 as col2, 3 as col3
UNION ALL SELECT 4, 5, 6
UNION ALL SELECT 7, 8, 9
"""
).getResultList()
);
}
}
With the code above, I can access http://localhost:8080/myEndpoint and see the result of the query.
However, the endpoint didn't appear in the endpoints listing that is showed in http://localhost:8080/. I had to type it manually in the browser. I would like the endpoint to be exposed in order to see it in Swagger.
Also, I have a feeling that there must be a better way to do this. And I want to learn.
I would like help to:
Get a solution that works and exposes the endpoint.
Understand what I did wrong and how to implement Kowalski's and Gagarwa's solutions.
Being able to expose the endpoint for the last solution (CustomQueryController).
Thanks in advance!
try changing your CustomQueryController to implement RepresentationModelProcessor
public class CustomQueryController implements RepresentationModelProcessor<RepresentationModel<RepositoryLinksResource>> {
and implementing the process method with:
#Override
public RepresentationModel<RepositoryLinksResource> process(RepresentationModel<RepositoryLinksResource> model) {
if (model instanceof RepositoryLinksResource) {
model.add(Link.of( "http://localhost:8080" + "/myEndpoint", "myEndpoint"));
}
return model;
}
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.customizing-json-output.representation-model-processor
I tried the first example that you have put here and it worked for me. But there is a bit of change. I have used PersistenceContext.
To return a Link as response I have used Link of WebMvcLinkBuilder.
Solution
In the below example I have used two tables Employee and Address in PostgresSQL . Both have area_code in common.
Interface
public interface CustomNativeRepository {
List<Object> runNativeQuery(Integer name);
}
Repository
#Repository
public class CustomNativeRepositoryImpl implements CustomNativeRepository {
Logger logger = LoggerFactory.getLogger(this.getClass());
#PersistenceContext
private EntityManager entityManager;
#Override
public List<Object> runNativeQuery(Integer areaCode) {
Query query = entityManager.createNativeQuery(
"Select e.first_name as name from employees e where e.area_code = ? "
+ "union all " +
"Select a.address as address from address a where a.area_code = ?");
query.setParameter(1, areaCode);
query.setParameter(2, areaCode);
List<Object> response = query.getResultList();
logger.info("Response from database: {}", response);
return response;
}
}
RestEndpoint Layer
#GetMapping(path ="/employee/{areaCode}")
public ResponseEntity<?> getEmployeeByCode(#PathVariable(value = "areaCode") Integer areaCode) throws NoSuchMethodException {
List<Object> response = customCustomerRepository.runNativeQuery(areaCode);
Link link = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(EmployeeController.class).getEmployeeByCode(areaCode)).withSelfRel();
return ResponseEntity.ok().body(CollectionModel.of(response, link));
}
Few examples which may help. link1 link2
Note: I have not created any Entity classes in my code base.
I want to transform a sql query in JPA.
SELECT status, count(*)
FROM abc
WHERE type='XXX'
GROUP BY status
I need something in a JPARepository with sql.
#Repository
public interface ABCRepository extends JpaRepository<abc, Long> {
long countByStatusAndType(final A type, final B status);
}
Is it Possible?
Firstly, create class containing parameters status and count for handling results, then create method in repository with query
#Query("SELECT status, count(*) as count FROM abc WHERE type=:type GROUP BY status")
List<CustomResultClass> countByStatus(String type);
Could some one help me how to read result set from JSON_VAL() in db2. I am having the below db2 query which am executing from CrudRepository named query from hibernate.
SELECT SYSTOOLS.BSON2JSON(data) FROM TABLE WHERE JSON_VAL(data,'column','s:25')=:value
Could some one help me how can I read the value.
My Hibernate repository class is
#Repository
public interface MyRepository extends CrudRepository<MyClass, Integer> {
#Query(value = "SELECT SYSTOOLS.BSON2JSON(data) FROM TABLE WHERE JSON_VAL(data,'column','s:25')=:value", nativeQuery = true)
com.ibm.db2.jcc.DB2Clob findById(#Param("idd") String id);
}
thanks in advance .
I am new to using CRUD Repository.
I have a database table with three columns:
course_id, name, course
I want to get a list of course_id give name, example,
SELECT id FROM table WHERE name='charmaine';
However, I do not want to do it with query but using crud repository.
There is an error shown in my controller.
May I know there is this error?
My controller
#GetMapping(value = "getting/{name}")
//#ResponseBody
public String getting(#PathVariable("name") String name) {
List<CourseMaster> getIds = CourseService.findIdByName(Name); —> error icon here
return getIds; —> error icon here
}
Service
public List<CourseMaster> findIdByName(String Name) {
return CourseMasterRepo.findByName(Name);
}
Repository
public interface CourseMasterRepo extends CrudRepository<CourseMaster, Long> {
List<CourseMaster> findByName(String Name);
}
You have to autowired service class in your controller like.
#Autowired
CourseService courseService;
#GetMapping(value = "getting/{name}")
public String getting(#PathVariable("name") String name) {
List<CourseMaster> getIds = courseService.findIdByName(Name);
return getIds;
}
if your code is done in java spring you must use hql language in hibernate, that is a interface of sql query.
hql query that use lambda expression is very simple and useful.
For example,
String hql = "FROM Employee E WHERE E.id = 10";
Query query = session.createQuery(hql);
List results = query.list();
Let's say I have to fire a query like this:
Select primarykey, columnname, old_value, new_value from first_audit_log;
Select primarykey, columnname, old_value, new_value from second_audit_log;
Select primarykey, columnname, old_value, new_value from third_audit_log; ...so on
audit_log is not mapped as JPA enity to any class and I strictly can't create n number of classes for n number of *_audit_logs.
Using native query feature, how best I can map this to a generic class? Trying to SELECT NEW feature, but not sure... Hence any help is appreciated.
Since your audit logs tables share the same columns, you can create a view that "unifies" those tables and map a single Java class to that view. I believe you can, since you don't need to write updates, I guess.
As an alternative, using native queries would be a good choice.
EDIT:
1) If your audit logs are already views, you can create a view based on other views, if you don't want to create a mapping Java class for each of them. Just remember to add a dummy column that has value 1 if the row comes from the "first" audit log, 2 if it comes from the second, and so on, so you can set them apart.
2) In order to use native queries, assuming your persistence provider is Hibernate, you can do like in this example:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
EntityManager em = emf.createEntityManager();
Session sess = em.unwrap(Session.class); // <-- Use Hibernate-specific features
SQLQuery query = sess.createSQLQuery(
"SELECT AVG(age) AS averageAge, AVG(salary) as averageSalary FROM persons");
query.setResultTransformer(Transformers.aliasToBean(MyResult.class));
MyResult result = (MyResult) query.list().get(0);
where MyResult is declared as follows:
public class MyResult {
private BigDecimal averageAge;
private BigDecimal averageSalary;
public BigDecimal getAverageAge() {
return averageAge;
}
public void setAverageAge(BigDecimal averageAge) {
this.averageAge = averageAge;
}
public BigDecimal getAverageSalary() {
return averageSalary;
}
public void setAverageSalary(BigDecimal averageSalary) {
this.averageSalary = averageSalary;
}
}
and the persons table is like this (MySQL syntax):
CREATE TABLE `persons` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`firstname` varchar(255) NOT NULL,
`lastname` varchar(255) NOT NULL,
`age` int(11) NOT NULL,
`salary` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
You can easily adapt this example to your needs, just replace persons and MyResult with what you want.
The aliases in the sql query is automatically converted to upper case and its looking for the setter in Upper case as a result org.hibernate.PropertyNotFoundException Exception is thrown. Any suggestions would be greatly appreciated.
For instance, the below statement is looking for the setter ID instead of Id/id (Could not find setter for ID on class Data)
List<Data> result = entityManager.unwrap(Session.class)
.createSQLQuery("Select id as id from table")
.setParameter("day", date.getDayOfMonth())
.setParameter("month", date.getMonthOfYear())
.setParameter("year", date.getYear())
.setResultTransformer(Transformers.aliasToBean(Data.class))
.list();
class Data {
Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}