I have a Spring Boot app with DataTables server-side processing and Oracle database. Actually, I started with implementing one of the tutorials. It worked. The tutorial uses JPA. I want to implement the same using JDBC. I made all the corresponding classes, the repository, the new model with same filds but without jpa. But when I tried to fetch the data, it allowed me to get only the first page without a chance to get to the second page. Below I will post the extracts of the original and added code. So, the original tutorial used these classes:
#Entity
#Table(name = "MYUSERS")
public class User {
#Id
#Column(name = "USER_ID")
private Long id;
#Column(name = "USER_NAME")
private String name;
#Column(name = "SALARY")
private String salary;
...getters and setters
}
And
#Entity
public class UserModel {
#Id
private Long id;
private String name;
private String salary;
private Integer totalRecords;
#Transient
private Integer rn;
...getters and setters
}
And I substituted these two classes with one like this:
public class NewUser {
private Long id;
private String name;
private String salary;
private Integer totalRecords;
private Integer rn;
...getters and setters
}
The table itself has only 3 fields: id, name and salary, the other 2 fields are created and filled later.
The repositiry the original Author has for the user looks like this:
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT * FROM MYUSERS", nativeQuery = true)
List<User> findAllByUsernames(List<String> listOfUsernames);
}
My own repository looks like this:
#Repository
public class NewUserRepoImpl extends JdbcDaoSupport implements NewUserRepo {
private static final String SELECT_ALL_SQL = "SELECT USER_ID as id, USER_NAME as name, SALARY as salary FROM MYUSERS";
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private final JdbcTemplate jdbctemplate;
public NewUserRepoImpl(NamedParameterJdbcTemplate namedParameterJdbcTemplate, JdbcTemplate jdbctemplate, DataSource dataSource) {
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
this.jdbctemplate = jdbctemplate;
setDataSource(dataSource);
}
#Override
public List<NewUser> findAll(PaginationCriteria pagination) {
try {
String paginatedQuery = AppUtil.buildPaginatedQueryForOracle(SELECT_ALL_SQL, pagination);
return jdbctemplate.query(paginatedQuery, newUserRowMapper());
} catch (DataAccessException e) {
throw new EntityNotFoundException("No Entities Found");
}
}
#Bean
public RowMapper<NewUser> newUserRowMapper() {
return (rs, i) -> {
final NewUser newUser = new NewUser();
newUser.setId(rs.getLong("ID"));
newUser.setName(rs.getString("NAME"));
newUser.setSalary(rs.getString("SALARY"));
newUser.setTotalRecords(rs.getInt("TOTAL_RECORDS"));
newUser.setTotalRecords(rs.getInt("RN"));
return newUser;
};
}
}
the buildPaginatedQueryForOracle thing transforms my Query and allows it to get the totalRecords and rn. Below I will post the output of it both for the orifinal and my queries (they are the same, I checked).
So, the main part, the controller. I left the old and new pieces in it for now for debug purposes and just returning one of the results:
#RequestMapping(value="/users/paginated/orcl", method=RequestMethod.GET)
#ResponseBody
public String listUsersPaginatedForOracle(HttpServletRequest request, HttpServletResponse response, Model model) {
DataTableRequest<User> dataTableInRQ = new DataTableRequest<User>(request);
System.out.println(new Gson().toJson(dataTableInRQ));
DataTableRequest<NewUser> dataTableInRQNew = new DataTableRequest<NewUser>(request);
System.out.println(new Gson().toJson(dataTableInRQNew));
PaginationCriteria pagination = dataTableInRQ.getPaginationRequest();
System.out.println(new Gson().toJson(pagination));
PaginationCriteria paginationNew = dataTableInRQNew.getPaginationRequest();
System.out.println(new Gson().toJson(paginationNew));
String baseQuery = "SELECT USER_ID as id, USER_NAME as name, SALARY as salary FROM MYUSERS";
String paginatedQuery = AppUtil.buildPaginatedQueryForOracle(baseQuery, pagination);
String paginatedQueryNew = AppUtil.buildPaginatedQueryForOracle(baseQuery, paginationNew);
System.out.println(paginatedQuery);
System.out.println(paginatedQueryNew);
Query query = entityManager.createNativeQuery(paginatedQuery, UserModel.class);
System.out.println("Query:");
System.out.println(query);
#SuppressWarnings("unchecked")
List<UserModel> userList = query.getResultList();
System.out.println(new Gson().toJson(userList));
#SuppressWarnings("unchecked")
List<NewUser> userListNew = newUserRepo.findAll(paginationNew);
System.out.println(new Gson().toJson(userListNew));
DataTableResults<UserModel> dataTableResult = new DataTableResults<UserModel>();
DataTableResults<NewUser> dataTableResultNew = new DataTableResults<NewUser>();
dataTableResult.setDraw(dataTableInRQ.getDraw());
dataTableResultNew.setDraw(dataTableInRQNew.getDraw());
dataTableResult.setListOfDataObjects(userList);
dataTableResultNew.setListOfDataObjects(userListNew);
if (!AppUtil.isObjectEmpty(userList)) {
dataTableResult.setRecordsTotal(userList.get(0).getTotalRecords()
.toString());
if (dataTableInRQ.getPaginationRequest().isFilterByEmpty()) {
dataTableResult.setRecordsFiltered(userList.get(0).getTotalRecords()
.toString());
} else {
dataTableResult.setRecordsFiltered(Integer.toString(userList.size()));
}
}
if (!AppUtil.isObjectEmpty(userListNew)) {
dataTableResultNew.setRecordsTotal(userListNew.get(0).getTotalRecords()
.toString());
if (dataTableInRQ.getPaginationRequest().isFilterByEmpty()) {
dataTableResultNew.setRecordsFiltered(userListNew.get(0).getTotalRecords()
.toString());
} else {
dataTableResultNew.setRecordsFiltered(Integer.toString(userListNew.size()));
}
}
System.out.println(new Gson().toJson(dataTableResult));
System.out.println(new Gson().toJson(dataTableResultNew));
return new Gson().toJson(dataTableResult);
}
So, I log out everything possible in the console. Here is the output:
{"uniqueId":"1579786571491","draw":"1","start":0,"length":5,"search":"","regex":false,"columns":[{"index":0,"data":"id","name":"ID","searchable":true,"orderable":true,"search":"","regex":false,"sortDir":"ASC"},{"index":1,"data":"name","name":"Name","searchable":true,"orderable":true,"search":"","regex":false},{"index":2,"data":"salary","name":"Salary","searchable":true,"orderable":true,"search":"","regex":false}],"order":{"index":0,"data":"id","name":"ID","searchable":true,"orderable":true,"search":"","regex":false,"sortDir":"ASC"},"isGlobalSearch":false,"maxParamsToCheck":3}
{"uniqueId":"1579786571491","draw":"1","start":0,"length":5,"search":"","regex":false,"columns":[{"index":0,"data":"id","name":"ID","searchable":true,"orderable":true,"search":"","regex":false,"sortDir":"ASC"},{"index":1,"data":"name","name":"Name","searchable":true,"orderable":true,"search":"","regex":false},{"index":2,"data":"salary","name":"Salary","searchable":true,"orderable":true,"search":"","regex":false}],"order":{"index":0,"data":"id","name":"ID","searchable":true,"orderable":true,"search":"","regex":false,"sortDir":"ASC"},"isGlobalSearch":false,"maxParamsToCheck":3}
{"pageNumber":0,"pageSize":5,"sortBy":{"mapOfSorts":{"id":"ASC"}},"filterBy":{"mapOfFilters":{},"globalSearch":false}}
{"pageNumber":0,"pageSize":5,"sortBy":{"mapOfSorts":{"id":"ASC"}},"filterBy":{"mapOfFilters":{},"globalSearch":false}}
SELECT * FROM (SELECT FILTERED_ORDERED_RESULTS.*, COUNT(1) OVER() total_records, ROWNUM AS RN FROM (SELECT BASEINFO.* FROM ( SELECT USER_ID as id, USER_NAME as name, SALARY as salary FROM MYUSERS ) BASEINFO ) FILTERED_ORDERED_RESULTS ORDER BY id ASC ) WHERE RN > (0 * 5) AND RN <= (0 + 1) * 5
SELECT * FROM (SELECT FILTERED_ORDERED_RESULTS.*, COUNT(1) OVER() total_records, ROWNUM AS RN FROM (SELECT BASEINFO.* FROM ( SELECT USER_ID as id, USER_NAME as name, SALARY as salary FROM MYUSERS ) BASEINFO ) FILTERED_ORDERED_RESULTS ORDER BY id ASC ) WHERE RN > (0 * 5) AND RN <= (0 + 1) * 5
Query:
org.hibernate.query.internal.NativeQueryImpl#3ea49a4
[{"id":3,"name":"user3","salary":"300","totalRecords":18},{"id":4,"name":"user4","salary":"400","totalRecords":18},{"id":5,"name":"user5","salary":"500","totalRecords":18},{"id":6,"name":"user6","salary":"600","totalRecords":18},{"id":7,"name":"user7","salary":"700","totalRecords":18}]
[{"id":3,"name":"user3","salary":"300","totalRecords":1},{"id":4,"name":"user4","salary":"400","totalRecords":2},{"id":5,"name":"user5","salary":"500","totalRecords":3},{"id":6,"name":"user6","salary":"600","totalRecords":4},{"id":7,"name":"user7","salary":"700","totalRecords":5}]
{"draw":"1","recordsFiltered":"18","recordsTotal":"18","data":[{"id":3,"name":"user3","salary":"300","totalRecords":18},{"id":4,"name":"user4","salary":"400","totalRecords":18},{"id":5,"name":"user5","salary":"500","totalRecords":18},{"id":6,"name":"user6","salary":"600","totalRecords":18},{"id":7,"name":"user7","salary":"700","totalRecords":18}]}
{"draw":"1","recordsFiltered":"1","recordsTotal":"1","data":[{"id":3,"name":"user3","salary":"300","totalRecords":1},{"id":4,"name":"user4","salary":"400","totalRecords":2},{"id":5,"name":"user5","salary":"500","totalRecords":3},{"id":6,"name":"user6","salary":"600","totalRecords":4},{"id":7,"name":"user7","salary":"700","totalRecords":5}]}
It helped me realize that:
DataTableRequest incoming from the back is the same for both jpa
and jdbc
PaginationCriteria are also the same
paginatedQuery
having been made with the method specified above are the same.
Differences are already seen in the Lists: where the Jpa list
retrieved with native Query has totalRecords as 18 for every row,
the JDBC repo with the same query returns 1,2,3... for every
subsequent row.
It made me think that I should look at the Query made for JPA. But, as you see in the log, System.out.println wasn't able to decipher it for some reason.
Any advice on how to decipher it and more importantly how to get the right total result for each row would be greatly appreciated!!!
I know we can attach listener to Guava Cache which will listen the removal events. But is there any way to listen updates to the cache elements ?
Use case :
I want to make my data base in consistent state. So when an object from cache is updated I need to write back to my data base.
Following is my sample code snippet :
Main class
public class TestCaching {
public static void main(String[] args) throws InterruptedException {
try {
Employee emp = getEmpUsingGuava(1);
System.out.println(emp.getName());
// Update the 'A' Employee
emp.setName(emp.getName() + " this is new");
emp = getEmpUsingGuava(2);
System.out.println(emp.getName());
// Update the 'B' Employee
emp.setName(emp.getName() + " this is new");
emp = getEmpUsingGuava(1);
System.out.println(emp.getName());
emp = getEmpUsingGuava(2);
System.out.println(emp.getName());
emp = getEmpUsingGuava(3);
System.out.println(emp.getName());
emp = getEmpUsingGuava(1);
System.out.println(emp.getName());
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public static Employee getEmpUsingGuava(int id) throws ExecutionException {
System.out.println("\nGetting employee by Id :- " + id);
LoadingCache<Integer, Employee> empCache = EmployeeGuavaCacheUtil.getLoadingCache();
System.out.println("Cache Size:" + empCache.size());
return empCache.get(id);
}
}
Employee class
public class Employee {
private int id;
private String name;
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
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;
}
}
EmployeeCacheUtil class
public class EmployeeGuavaCacheUtil {
private static LoadingCache<Integer, Employee> empCache;
static {
empCache = CacheBuilder.newBuilder().maximumSize(2).expireAfterWrite(1, TimeUnit.SECONDS)
.build(new EmployeeCacheLoader());
}
public static LoadingCache<Integer, Employee> getLoadingCache() {
return empCache;
}
}
EmployeeCacheLoader class
public class EmployeeCacheLoader extends CacheLoader<Integer, Employee> {
#Override
public Employee load(Integer id) throws Exception {
return getEmployeeById(id);
}
public static Employee getEmployeeById(int id) {
System.out.println("Getting employee...");
Employee emp1 = new Employee(1, "A");
Employee emp2 = new Employee(2, "B");
Employee emp3 = new Employee(3, "C");
Employee emp4 = new Employee(4, "D");
switch (id) {
case 1:
return emp1;
case 2:
return emp2;
case 3:
return emp3;
case 4:
return emp4;
}
return null;
}
}
Following is output when i run the program :
Getting employee by Id :- 1
Cache Size:0
Getting employee...
A
Getting employee by Id :- 2
Cache Size:1
Getting employee...
B
Getting employee by Id :- 1
Cache Size:2
A this is new
Getting employee by Id :- 2
Cache Size:2
B this is new
Getting employee by Id :- 3
Cache Size:2
Getting employee...
C
Getting employee by Id :- 1
Cache Size:2
Getting employee...
A
See when I update the 'A' and 'B' employee, I want to update it to my DataBase. Cached 'A' and 'B' gets updated and returned correctly.
Note : This is sample code got from google search and changed according to my requirement.
It sounds like what you are interested is the proposed Guava feature, CacheWriter albeit one that can listen to changes to the objects as well.
Generally, people would use an ORM layer such as hibernate to cover this use case, I do not think there is a way to handle this using Guava alone.
Edit: I just discovered that caffeine has write-back capability. Note that caffeine has some limited compatibility with Guava Caches.
I am using Spring MVC with JdbcTemplate and a MySQL database.
Say I have the following 2 tables :
table_school
ID NAME
table_students
ID NAME ADDRESS SCHOOL_ID
I have a School POJO that has the following class variables :
int id, String name, List<Student> students
Is there a way of retrieving a List with each School object containing the appropriate List of Student objects using JdbcTemplate in one query? I know this is easily achievable using Hibernate but I would like to use JdbcTemplate ..
Many thanks !
Yes, you can fetch all data in 1 query.
Simple example:
class Student {
int id;
String name;
String addr;
Student(int id, String name, String addr) {
this.addr = addr;
this.id = id;
this.name = name;
}
}
class School {
int id;
String name;
List<Student> students = new ArrayList<>();
School(int id, String name) {
this.id = id;
this.name = name;
}
void addStudent(Student s) {
students.add(s);
}
}
/*
* helper method that gets school from map or create if not present
*/
private School getSchool(Map<Integer, School> schoolMap, int id, String name) {
School school = schoolMap.get(id);
if (school == null) {
school = new School(id, name);
schoolMap.put(id, school);
}
return school;
}
// RUN QUERY
String sql =
" select st.ID, st.NAME, st.ADDRESS. s.id, s.name" +
" from table_students st" +
" inner join table_school s on st.school_id = s.id";
final Map<Integer, School> schoolMap = new HashMap<>();
jdbcTemplate.query(sql, new RowCallbackHandler() {
#Override
public void processRow(ResultSet rs) throws SQLException {
int studentId = rs.getInt(1);
String studentName = rs.getString(2);
String studentAddr = rs.getString(3);
int schoolId = rs.getInt(4);
String schoolName = rs.getString(5);
Student student = new Student(studentId, studentName, studentAddr);
getSchool(schoolMap, schoolId, schoolName).addStudent(student);
}
});
One final point regarding fetching performance:
If you expect many records to fetch it is nearly always a good idea to increase jdbc fetch size parameter. So before run query set it on your jdbcTemplate:
jdbcTemplate.setFetchSize(200); // you can experiment with this value
or if you are using spring's JdbcDaoSupport you can use such pattern:
public class MyDao extends JdbcDaoSupport {
....
#Override
protected void initTemplateConfig() {
getJdbcTemplate().setFetchSize(200);
}
}
I am trying to set the Parent List in a ParameterizedRowMapper how is this written or approached. I have two Objects one for parent and one for children however children contains a ListThe parents for each child are stored in a separate table in the database and the mapping is 1 - many.
The select for the records for the parents will be done in a separate ResultSet. Will the mapping have to be done separately (separate ParameterizedRowMapper), if so how will i have to write the ParameterizedRowMapper this is the major concern how ParameterizedRowMapper is written to accommodate a list items.
ParameterizedRowMapper
public static class ChildrenMapper implements ParameterizedRowMapper<Children>{
public Children mapRow(ResultSet rs, int rowNum) throws SQLException {
Children child = new Children();
child.setFirstName(rs.getString("firstName"));
child.setLastName(rs.getString("lastName"));
//a child can have many Parents or gaurdians
child.setParent(List<Parent>);
return child;
}
}
Based on my research i have found that i need to use ResultSetExtractor, however i have a questions on the use of that. Do i integrate it into the class at the point of setting the Parent? Can someone guide me on how it can be done the correct way
Children.java
Public class Children(){
int cid;
String firstName;
String lastName;
List<Parent>parents;
..
//getters/setters
}
Parent.java
Public class Parent(){
int pid;
String firstName;
String lastName;
..
//setters/getters
}
I will show how to do this for a canonical 1-to-many example, you can adapt it to your vo class / table.
Order class
public class Order {
private Long orderId;
private String user;
private List<LineItem> items;
// Getter / setter omitted
}
Item class
public class LineItem {
private Long lineItemId;
private String product;
private int quantity;
// Getter / setter omitted
}
Use two rowmappers one for each class and then use a result set extractor to convert multiple rows into one order + line items
OrderRepository
public final static RowMapper<Order> orderMapper = ParameterizedBeanPropertyRowMapper.newInstance(Order.class);
public final static RowMapper<LineItem> lineItemMapper = ParameterizedBeanPropertyRowMapper.newInstance(LineItem.class);
public Order findOrderWithItems(Long orderId) {
return jdbcTemplate.query("select * from orders, line_item "
+ " where orders.order_id = line_item.order_id and orders.order_id = ?",
new ResultSetExtractor<Order>() {
public Order extractData(ResultSet rs) throws SQLException, DataAccessException {
Order order = null;
int row = 0;
while (rs.next()) {
if (order == null) {
order = orderMapper.mapRow(rs, row);
}
order.addItem(lineItemMapper.mapRow(rs, row));
row++;
}
return order;
}
}, orderId);
}
public List<Order> findAllOrderWithItmes() {
return jdbcTemplate.query("select * from orders, line_item "
+ " where orders.order_id = line_item.order_id order by orders.order_id",
new ResultSetExtractor<List<Order>>() {
public List<Order> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<Order> orders = new ArrayList<Order>();
Long orderId = null;
Order currentOrder = null;
int orderIdx = 0;
int itemIdx = 0;
while (rs.next()) {
// first row or when order changes
if (currentOrder == null || !orderId.equals(rs.getLong("order_id"))) {
orderId = rs.getLong("order_id");
currentOrder = orderMapper.mapRow(rs, orderIdx++);
itemIdx = 0;
orders.add(currentOrder);
}
currentOrder.addItem(lineItemMapper.mapRow(rs, itemIdx++));
}
return orders;
}
});
}