How to use inner join with Spring MVC - java

Hello I wrote inner join between the foreign key and the linked tables, but I can not print the data in the 2nd table. I get an error in Javascript, probably the data in the 2nd table is not coming there. I need user_name in User table but I can not access.
BookController
#Autowired
private BooksService booksService;
#Autowired
private BookCommentsService bookCommentsService;
#RequestMapping(value = "/bookdetail")
public ModelAndView showBookDetailForm(#RequestParam long book_id)
{
ModelAndView mav= new ModelAndView("bookdetail");
List<Books> book=booksService.bookGetWithId(book_id);
List<BookComments> listBookComments= bookCommentsService.listAllDetailComments(book_id);
mav.addObject("listBook",book);
mav.addObject("listBookComments", listBookComments);
return mav;
}
BookCommentsService
#Service
public class BookCommentsService {
#Autowired
private BookCommentsRepository repository;
public List<BookComments> listAllDetailComments(long book_id)
{
return repository.listAllDetailComments(book_id);
}
}
BookCommentsRepository
public interface BookCommentsRepository extends CrudRepository<BookComments, Long> {
#Query(value = "Select b From BookComments b inner join Users u on b.comment_user_id = u.user_id where b.comment_book_id= :book_id")
public List<BookComments> listAllDetailComments(#Param("book_id") long book_id);
}
BookDetail.js
<c:forEach items="${listBookComments}" var="bookcomments">
(No problem here) ${bookcomments.book_comment}
(The error is here) <h4>${bookcomments.user_name}</h4>
</c:forEach>

You should use comment_user_id and close the braces, ${bookcomments.comment_user_id} should work fine.

To solve your problem, you must use another object (known as a Data Transfer Object; DTO) which will contain the details from BookComments and the username.
An example would be:
package com.myapp.book;
public class BookCommentsDto{
private String bookComment;
private String username;
// You can add any other attributes that you need from the table book_comments
// add them in the constructor as well
public BookCommentsDto(String bookComment, String username){
this.bookComment = bookComment;
this.username = username;
}
}
I will refer to the class BookCommentsDto as dto. The above dto is the simple dto you might need. You can add other details you need from book comment in the dto and make sure to add them in the constructor as well. I have included a dummy package name on the top because we need to mention the full classpath of the dto in the query we will write. In the query below, I am creating the dto from the result of the sql. In the query, I am using the constructor of BookCommentsDto to create the object which will be returned.
#Query(value = "Select new com.myapp.book.BookCommentsDto(b.book_comment, u.user_name) From BookComments b inner join Users u on b.comment_user_id = u.user_id where b.comment_book_id= :book_id")
public List<BookCommentsDto> listAllDetailComments(#Param("book_id") long book_id);
Update return type in the service BookCommentsService
#Service
public class BookCommentsService {
#Autowired
private BookCommentsRepository repository;
public List<BookCommentsDto> listAllDetailComments(long book_id)
{
return repository.listAllDetailComments(book_id);
}
}
Update controller
#RequestMapping(value = "/bookdetail")
public ModelAndView showBookDetailForm(#RequestParam long book_id)
{
ModelAndView mav= new ModelAndView("bookdetail");
List<Books> book=booksService.bookGetWithId(book_id);
List<BookCommentsDto> listBookCommentsDto= bookCommentsService.listAllDetailComments(book_id);
mav.addObject("listBook",book);
mav.addObject("listBookCommentsDto", listBookCommentsDto);
return mav;
}
Finally update js
<c:forEach items="${listBookCommentsDto}" var="bookcommentsDto">
${bookcommentsDto.bookComment}
<h4>${bookcommentsDto.username}</h4>
</c:forEach>
if you do the above changes, the code should work just fine.
I have a few comments for you if you want to improve your code and get better at coding.
When using Hibernate, we must think in terms of entities instead of tables found in the DB. Therefore we do not do inner joins like we do in native sql, instead we use hibernate relationship to define the relationship between the table and we join the entities instead of the tables. You can read on OneToMany, ManyToOne and ManyToMany relationships when it comes to joining entities.
List<Books> book=booksService.bookGetWithId(book_id);
A book_id is returning a list of Books? book_id should be unique per book if ids are used correctly and even if it is returning a list of books, the generic of the list should be Book, not Books; List<Book>, not List<Books>.
When hibernate is implemented correctly, you should be able to retrieve all the data at once and it would be something like:
This is the type of code is which you should be able to achieve after working with hibernate for a while (appx 1 year), however there is still a lot that can be improved about it.
In book's repository
#Query("from book b inner join b.comments c inner join c.users where b.book_id = :book_id")
Book getBook(#Param("book_id") long book_id); // bookService.getBook will call this method
Controller level
Book book = bookService.getBook(bookId); // only this one is querying the DB
List<BookComment> bookComments = book.getComments();
List<User> users = book.getComments().getUsers();
Advance topics to read on (read in same order as posted):
HQL - Hibernate Query Language
N+1 issue with Hibernate
JPA Specifications (for creating dynamic queries)
QueryDsl - an improvement of JPA Specifications

Related

Change table on runtime - Spring API Rest

Now, I have the next entity. This one is the m1 table of my database.
#Entity(name = "m1")
#Data
public class Information {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String date;
private Double weight_1;
private Double weight_2;
private Double weight_3;
private Double weight_4;
private int working;
}
So, when I do some call to the APIRest it returns me the information corresponding to the m1 table. The controller that I have is the next (simple controller that returns all the information):
#Controller
#RequestMapping(path = "/information")
public class InformationController {
#Autowired
private InformationRepository repository;
#GetMapping(path="/all")
public #ResponseBody List<Information> getAllInformations() {
// This returns a JSON or XML with the users
return repository.findAll();
}
}
The question is: There is any way to change the name of the m1 on runtime. For example can I put the name of the table in the call path and in the API Rest take it?
Maybe this is impossible and I am doing it the bad way I do not know.
EDIT: I mean, can I change the table that the API Rest is taking the data by putting the table that I want in the url/path that I call. For example: in my case the default table/entity that the APIRest take the data is m1, so can I call http://localhost:8080/information/especifictable/all/ where especific table is the table that I want the recieve the data of the database and in the API Rest take that url parameter and change the default m1 with the especifictable.
I do not know if I have explained it well, I do not know how to explain it well.
Such a design would only make sense, if there are two tables in DB, which look the same. if that is the case there is something wrong with your DB design.
Basically it is not possible, to the best of my knowledge.

Updating entity with only non-null attributes of passed in object

I have an entity type with several fields. Some of them are references to other entities. I want to create a Rest API endpoint in Spring that lets users to update entities of this type.
Assume that I have an entity User that contains a list of friends. I only want to let users update some specific fields, such as name, age and description, of a user entity, and not list of friends.
Besides, I only want those attribute of the entity to be updated that the corresponding passed in values are not null.
public class UserController {
#RequestMapping(path="",method=RequestMethod.PUT)
public void update(#RequestBody User user) {
userService.save(user);
}
}
How can I make this possible ?
Instead of taking User object as a parameter in your update() method you can take a DTO class that only defines the properties you need to change.
Define a class UpdateUserDTO like so
public class UpdateUserDTO {
private String name;
private String description;
//other fields you want the clients to change.
...
}
Now this UpdateUserDTO can be used as a data transfer object in your update method like so.
public class UserController {
#RequestMapping(path="",method=RequestMethod.PUT)
public void update(#RequestBody UpdateUserDTO dto) {
//validate your dto properties and then update your user entity.
userService.save(user);
}
}
Session session = sessionFactory.getCurrentSession();
String newuserId=newuser.getUserId();
session.clear();
User user=userService.readUser(newuserId);
Property1 prop1=user.getProperty1();
List<Property2> prop2=user.getProperty2();
session.clear();
newuser.setProp1(prop1);
newuser.setProp2(prop2);
newuser.save(); //You should go through services and abstraction layers before saving aka it should be in the DAO implementation layer.
Your question needs improvement but try this answer.
You basically save the missing properties in some variables and you append them to your front end User object.
Based on the HQL output it seems like this way gets the user and adds the other properties to it.

How to save a JavaBean contains list to Sql By using Mybatis?

As we all know, when using Hibernate, it can create sql tables for us. Even a Java Bean has a list, hibernate will create a foreign table for us.
However, when I use myBatis, I find it very inconvenient that I have to create the table by myself ahead. Then I can insert values to the table. What is more inconvenient and I am not sure is that when I have a JavaBean with a list, I want to save this document to mysql.
For example, My java bean:
public class Person {
public String id;
public List<String> interests;
}
Then, Mysql should have a primary table (person table) and a foreign table (interests table).
My question is: 1. Can mybatis create these two tables for me?
2. Can mybatis auto convert the javabean for me and insert values to both two tables. For instance (of course this is not correct)
<insert id="insertPerson">
INSERT INTO Person.java TO Database
</insert>
No, you do not want it. Do not use MyBatis in the same way as Spring Data or Hibernate.
About auto conversion. You should provide mapping for all related types (PersonInterest in our condition). Handle correct insertion on your Service layer. Selection could be provided by MyBatis with #Many(select = "selectPersonInterests")
#Mapper
public interface PersonMapper {
#InsertProvider(type = PersonProvider.class, method = "insertPerson")
void insertPerson(#Param("person") Person person);
#InsertProvider(type = PersonProvider.class, method = "insertPersonInterest")
void insertInterestItem(#Param("interest") Interest item);
}
#Component
public class PersonProvider {
public String insertPersonInterest() {
return "insert into person_interest (...) " +
"values (#{...}, ...);";
}
public String insertPerson() {
return "insert into person (...) " +
"values (#{...}, ...);";
}
}

Spring JPA - Best way to update multiple fields

I'm new to using JPA and trying to transition my code from JdbcTemplate to JPA. Originally I updated a subset of my columns by taking in a map of the columns with their values and created the SQL Update string myself and executed it using a DAO. I was wondering what would be the best way to do something similar using JPA?
EDIT:
How would I transform this code from my DAO to something equivalent in JPA?
public void updateFields(String userId, Map<String, String> fields) {
StringBuilder sb = new StringBuilder();
for (Entry<String, String> entry : fields.entrySet()) {
sb.append(entry.getKey());
sb.append("='");
sb.append(StringEscapeUtils.escapeEcmaScript(entry.getValue()));
sb.append("', ");
}
String str = sb.toString();
if (str.length() > 2) {
str = str.substring(0, str.length() - 2); // remove ", "
String sql = "UPDATE users_table SET " + str + " WHERE user_id=?";
jdbcTemplate.update(sql, new Object[] { userId },
new int[] { Types.VARCHAR });
}
}
You have to read more about JPA for sure :)
Once entity is in Persistence Context it is tracked by JPA provider till the end of persistence context life or until EntityManager#detach() method is called. When transaction finishes (commit) - the state of managed entities in persistence context is synchronized with database and all changes are made.
If your entity is new, you can simply put it in the persistece context by invoking EntityManager#persist() method.
In your case (update of existing entity), you have to get a row from database and somehow change it to entity. It can be done in many ways, but the simpliest is to call EntityManager#find() method which will return managed entity. Returned object will be also put to current persistence context, so if there is an active transaction, you can change whatever property you like (not the primary key) and just finish transaction by invoking commit (or if this is container managed transaction just finish method).
update
After your comment I can see your point. I think you should redesign your app to fit JPA standards and capabilities. Anyway - if you already have a map of pairs <Attribute_name, Attrbute_value>, you can make use of something called Metamodel. Simple usage is shown below. This is naive implementation and works good only with basic attributes, you should take care of relationships etc. (access to more informations about attributes can be done via methods attr.getJavaType() or attr.getPersistentAttributeType())
Metamodel meta = entityManager.getMetamodel();
EntityType<User> user_ = meta.entity(User.class);
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<User> update = cb.createCriteriaUpdate(User.class);
Root e = update.from(User.class);
for( Attribute<? super User, ?> attr : user_.getAttributes() ) {
if (map.containsKey(attr.getName())) {
update.set(attr, map.get(attr));
}
}
update.where(cb.equal(e.get("id"), idOfUser));
entityManager.createQuery(update).executeUpdate();
Please note that Update Criteria Queries are available in JPA since 2.1 version.
Here you can find more informations about metamodel generation.
Alternatively to metamodel you can just use java reflection mechanisms.
JPA handles the update. Retrieve a dataset as entity using the entitymanager, change the value and call persist. This will store the changed data in your db.
In case you are using Hibernate(as JPA provider), here's an example
Entity
#Entity
#Table(name="PERSON")
public class Person {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name="NAME", nullable=false)
private String name;
other fields....
}
DAO
public interface PersonDao {
Person findById(int id);
void persist(Person person);
...
}
DaoImpl
#Repository("personDao")
public class PersonDaoImpl extends AnAbstractClassWithSessionFactory implements PersonDao {
public Person findById(int id) {
return (Person) getSession().get(Person.class, id);
}
public void persist(Person person){
getSession().persist(person);
}
}
Service
#Service("personService")
#Transactional
public class PersonServiceImpl implements PersonService {
#Autowired
PersonDao personDao;
#Override
public void createAndPersist(SomeSourceObject object) {
//create Person object and populates with the source object
Person person = new Person();
person.name = object.name;
...
personDao.persist(person);
}
#Override
public Person findById(int id) {
return personDao.findById(id);
}
public void doSomethingWithPerson(Person person) {
person.setName(person.getName()+" HELLO ");
//here since we are in transaction, no need to explicitly call update/merge
//it will be updated in db as soon as the methods completed successfully
//OR
//changes will be undone if transaction failed/rolledback
}
}
JPA documentation are indeed good resource for details.
From design point of view, if you have web interfacing, i tends to say include one more service delegate layer(PersonDelegateService e.g.) which maps the actual data received from UI to person entity (and viceversa, for display, to populate the view object from person entity) and delegate to service for actual person entity processing.

Mapping ResultSet to Pojo Objects

Well that's really embarrassing I have made a standard pojo class and its dao class for data retrieval purpose. I am having a difficulty to understand a basic procedure to how to handle a customized query data to Pojo class.
let's say my User class is
public class User{
private int userId;
private String username;
private int addressId;
}
public class Address{
private int addressId;
private String zip;
}
public class UserDAO{
public void getUserDetails(){
String getSql = select u.userId, u.username, a.zipcode from user u, address a where u.addressId = a.addressId;
//no pojo class is now specific to the resultset returned. so we can't map result to pojo object
}
}
now how I should model this with my pojo class as if using String to manage this then concept of object oriented vanishes, also complexity would increase in the future as well. kindly guide!
Update for Further Explanation
We know that we can map same table objects with same pojo class, but when the query is customized and there is a data returned which doesn't map to any specific class then what would be the procedure? i.e. should we make another class? or should we throw that data in a String variable? kindly give some example as well.
For this purpose you can use one of implementation of JPA. But as you want to do it manually I will give you small example.
UPD:
public class User {
private int userId;
private String username;
private Address address; // USE POJO not ID
}
public class Address{
private int addressId;
private String zip;
List<User> users;
}
public User getUserById(Connection con, long userId) {
PreparedStatement stmt;
String query = "select u.user_id, u.user_name, a.id, a.zip from user u, address a where a.address_id = u.id and u.id = ?";
User user = new User();
Address address = new Address;
try {
stmt = con.prepareStatement(query);
stmt.setLong(1, userId);
ResultSet rs = stmt.executeQuery();
address.setId(rs.getInt("id"));
address.setZip(rs.getString("zip");
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("user_name"));
user.setAddressId(rs.getInt("address_id"));
user.setAddress(address); // look here
} catch (SQLException e) {
if (con != null) {
try {
System.err.print("Transaction is being rolled back");
con.rollback();
} catch (SQLException excep) {
}
}
} finally {
if (stmt != null) {
stmt.close();
}
}
return user;
}
You shouldn't do new POJO for that query, you should write normal query. And remember - your object model is main, tables in DB is just a way to save data of your application.
We know that we can map same table objects with same pojo class, but when the query is customized and there is a data returned which doesn't map to any specific class then what would be the procedure? i.e. should we make another class?
JPA dynamic instantiation allows you to define a query with a POJO whose constructor specifies only the fields and types you want from the database.
This will perform a JPA selection which will return a List.
If you need to change the query later and the columns are unchanged, your POJO will still work.
If you change the columns, then also change the POJO accordingly.
NOTE:
You must specify fully qualified package and constructor arguments.
Type User must be a JPA-mapped or JPA-annotated entity class.
The entityManager is in JPA EntityManagerFactory.
TypedQuery<User> q;
String sql = "select new com.stuff.User(
int u.userId, String u.username, String a.zipcode)
from User u, Address a where u.addressId = a.addressId";
List<User> list = entityManager.createQuery(sql).getResultList();
for(User u : list) {
doStuff(u);
}
Dynamic instantiation is also handy when you want to select specified columns, but avoid those columns with large data, such as BLOB types.
For example, maybe you want a list of proxy POJO's which represent the fully populated thing, but are themselves not fully populated.
You present the proxy list, and when the user selects one, then you do another query to get the fully populated object.
Your mileage may vary.
There's many ORM frameworks that can do this including Hibernate, myBatis, JPA and spring-JDBC
spring-jdbc and myBatis give you granular control over the SQL whereas with JPA and Hibernate you are usually abstracted away from the SQL.
I suggest you do some reading and figure out which one you like before rolling your own solution.
Your question:
We know that we can map same table objects with same pojo class,
but when the query is customized and there is a data returned
which doesn't map to any specific class then what would be the procedure?
If you have 100 kinds of SQL which returns different combination of columns, could it be to create 100 different POJOs? The answer is "NO, stop using POJO".
This library qood is designed to solve this problem, you can try it.

Categories