Hibernate: How to display data from multiple table - spring

I am new in spring/hibernate technologies, I have tried to find an information about it, but failed, so if you can help I will be so thankful!
I need to display a JSON response in browser of multiple tables, one of the table has primary key for another one.
My entities:
#Entity
#Table
#ToString
public class Book {
#Id
#GeneratedValue(strategy = AUTO)
#JsonView(Views.IdName.class)
private Long book_id;
#JsonView(Views.IdName.class)
private String name;
#Column(length = 1000000)
private String text;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="author_id")
#JsonView(Views.IdName.class)
private Author author;
// ....get/set methods
Another one:
#Entity
#Table
#ToString
public class Page {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
#Column(length = 1000000)
private String text;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "book_id")
private Book book;
// ...get/set methods
My controllers:
#RestController
#RequestMapping("books")
public class BookController {
private final BookRepo bookRepo;
#Autowired
public BookController(BookRepo bookRepo) {
this.bookRepo = bookRepo;
}
#GetMapping
#JsonView(Views.IdName.class)
public List<Book> getAll() {
return bookRepo.findAll();
}
#GetMapping("{id}")
public Book getOne(#PathVariable("id") Book book) {
return book;
}
}
Another one:
#RestController
#RequestMapping("authors")
public class AuthorController {
private final AuthorRepo authorRepo;
#Autowired
public AuthorController(AuthorRepo authorRepo) {
this.authorRepo = authorRepo;
}
#GetMapping
public List<Author> getAll() {
return authorRepo.findAll();
}
#GetMapping("{id}")
public Optional<Author> getOne(#PathVariable("id") Long id) {
return authorRepo.findById(id);
}
}
And also repo for interaction with DB (they are the similar):
public interface AuthorRepo extends JpaRepository<Author, Long> {
}
So when I make a request for get all books, I take the following JSON:
enter image description here
Bit I want different result, something like:
[
{
"book_id" : 1,
"name": "name 1 book",
"author" :
{
"author_id" : 1,
"name": "some_name"
}
}
]
Also, when I tried to make a request for /authors/1, I will get the following response (something like recursion) :
enter image description here
So any help how can I handle with it? Thank you!

You can use a #NoRepositoryBean
like in this example:
#NoRepositoryBean
public interface MappedTypeRepository<T extends AbstractMappedType>
extends Repository<T, Long> {
#Query("select new com.example.YourObjectWithConstructor(e.attribute, sub.sub_attribute) from entity e inner join e.subtable sub where e.attribute = ?1")
List<YourObjectWithConstructor> findAllByAttribute(String attribute);
}
My example may not be 100% correct, I did not check the syntax. Feel free to explore it
Check this also:
JPQL Create new Object In Select Statement - avoid or embrace?

Related

Spring hibernate ignore json object

I need to remove cart object from json, but only in one controller method and that is:
#GetMapping("/users")
public List<User> getUsers() {
return userRepository.findAll();
}
User
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotBlank(message = "Name cannot be empty")
private String name;
#OneToOne
private Cart cart;
}
Cart
#Entity
public class Cart {
#Id
private String id = UUID.randomUUID().toString();
#OneToMany
private List<CartItem> cartItems = new ArrayList<>();
#OneToOne
#JsonIgnore
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
}
I have done it with simple solution so i loop trough all users, and set their cart to null,and then anotated user entity with #JsonInclude(JsonInclude.Include.NON_NULL)
But i dont think this is propper solution, so im searching for some better solution..
How am i able to do this?
Thanks...
You can create DTO (data transfer object) class like this:
#Data
public class UsersDto {
private Integer id;
private String name;
public UsersDto(User user) {
this.id = user.id;
this.name= user.name;
}
}
and than create List<UsersDto>
#GetMapping("/users")
public List<UsersDto> getUsers() {
List<User> users = userRepository.findAll();
return users
.stream()
.map(o -> new UsersDto(o))
.collect(Collectors.toList());
}
You should use Data Projection.
In your use case, you can use an interface projection:
public interface CartlessUser {
Integer getId();
String getName();
}
And In your repository:
public interface UserRepository extends JpaRepository<User, Integer> {
List<CartlessUser> findAllBy();
}
The interface projection will help generate the sql query for only selecting the id, name fields. This will save you from fetching the Cart data when you're just going to throw it away anyways.

Spring Boot JPA returns List of List not List of Object

I am new to spring boot jpa. I would like to get return type as List but I am getting just List
My Entity Class
#Component
#Entity
#Table(name = "USERDB.USERS")
public class User() {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MY_SEQ")
#SequenceGenerator(sequenceName = "MY_SEQ_NAME", allocationSize = 1), name = "MY_SEQ")
#Column(name = "userId")
private long id;
#Column(name = "firstName")
private String fName;
#Column(name = "midName")
private String mName;
#Column(name = "lastName")
private String lName;
#Column(name = "email")
private String email;
#Column(name = "createdDate")
private Timestamp createdOn;
public User() {
this.createdOn = new Timestamp(System.currentTimeMillis()
}
//SETTERS & GETTERS
}
My Repository;
public interface UserRepository extends JpaRepository<User, String> {
#Query("SELECT id fName, lastName, email FROM User u WHERE u.fName=(:fName)")
public List<User> findByEmail(#Param("fName") String fName);
}
All I wanted is to get a json response as a User Array with key value pair like below
[
[
"id": 1001,
"fName": John",
"lName": "Doe",
"email": "johnd#example.com"
],
[
"id": 1002,
"fName": "John",
"lName": "Simmons",
"email": "johns#example.com"
],
]
but I am getting a list with just values as below.
[
[
1001,
"John",
"Doe",
"johnd#example.com"
],
[
1002,
"John",
"Simmons",
"johns#example.com"
],
]
I am not sure where I am doing wrong or is this how I am supposed to get ? This is a hypothetical example of my actual program. Please excuse me for any errors.
Here is my controller class
#Restcontroller
public class UserController {
#Autowired
UserRepository repo;
#GetMapping("/user/{fname}")
public List<User> getUserByName(
#PathVariable("fname") String fname) {
return repo.findByEmail(fname);
}
}
A few points on your code:
public interface UserRepository extends JpaRepository<User, String> {
Query("SELECT id fName, lastName, email FROM User u WHERE u.fName=(:fName)")
public List<User> findEmails(#Param("fName") String fName);
}
You are creating the repository with JpaRepository<User, String> but in your class User id is not of type String. Consider making the ID field a String.
The method is findEmails but it is returning a List<User>? I would expect such a function to return List<String> - each string of course being an email. You may consider renaming this function to avoid future confusion. Not a big deal though.
In your query:
#Query("SELECT id fName, lastName, email FROM User u WHERE u.fName=(:fName)")
You should change this to:
#Query("SELECT u.id u.fName, u.lastName, u.email FROM User u WHERE u.fName=(:fName)")
That should fix the serialization issue you are having.
I very much agree with the suggestion of Fermi-4. Using a better naming convention is key to have a better easy manageable code. You should follow them. To solve your problem, just do the following and it will solve your problem.
public interface UserRepository extends JpaRepository<User, long> {
public List<User> findByFname(String fName);
}
Also, consider changing USER Entity definition to add serializable implementation as below
#Component
#Entity
#Table(name = "USERDB.USERS")
public class User implements Serializable{
/**
*
*/
private static final long serialVersionUID = 3L;
Create additional User constructor with only few arguments that you wish to fetch from table
public User(long id, String fName, String lastName, String email) {
this.id = id;
this.fName = fName;
this.lastName = lastName;
this.email = email;
}
Adjust JP query like following
#Query("SELECT new com.package.User(u.id, u.fName, u.lastName, u.email) FROM User u WHERE u.fName = :fName")
public List<User> findByEmail(#Param("fName") String fName);
replace com.package with your User's actual package

Limiting the visibility of results from the database on one page- spring boot

I create web application in spring boot using the postgress database.
I want to limit the number of records per page(now it's 30,000 records - it's loading a long time), so what should i do to limit it? I use thymeleaf.
Model:
#Entity(name="articles")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Articles {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long article_id;
private String title;
private String issn;
private String eissn;
private String title2;
private String issn2;
private String eissn2;
private Integer points;
#ManyToMany
#JoinTable(
name = "articles_categories",
joinColumns = #JoinColumn(name = "article_id"),
inverseJoinColumns = #JoinColumn(name = "category_id"))
private List<Category> categories;
....
getters and setters
Repository:
public interface ArticlesRepository extends JpaRepository<Articles,Long> {
}
Controller:
#Controller
#RequestMapping("/articles")
public class ArticlesController {
private ArticleService articleService;
#Autowired
public void setArticleService(ArticleService articleService) {
this.articleService = articleService;
}
#GetMapping
public String getAll(Model model)
{
model.addAttribute("articles", articleService.list());
return "articles";
}
Service:
#Service
public class ArticleService {
#Autowired
private ArticlesRepository articlesRepository;
public ArticleService() {
}
public List<Articles> list(){
return articlesRepository.findAll();
}}
Use Pageable to limit the size of your articles.
public List<Articles> list(int page, int limit){
Page<Articles> pageableArticales = articlesRepository.findAll(PageRequest.of(page, limit);
return pageableArticales.getContent();
}
Note that repository.findAll(pageable) wraps the list of data on Page, which provides getNumber(), getSize(), getNumberOfElements(), getTotalPages(), getTotalElements, etc.
And consider exploring PageRequest and PagedResources as well.

Spring Data Rest #EmbeddedId cannot be constructed from Post Request

I have a JPA entity Person and an entity Team. Both are joined by an entity PersonToTeam. This joining entity holds a many-to-one relation to Person and one to Team. It has a multi-column key consisting of the ids of the Person and the Team, which is represented by an #EmbeddedId. To convert the embedded id back and forth to the request id I have a converter. All this follows the suggestion on Spring Data REST #Idclass not recognized
The code looks like this:
#Entity
public class PersonToTeam {
#EmbeddedId
#Getter
#Setter
private PersonToTeamId id = new PersonToTeamId();
#ManyToOne
#Getter
#Setter
#JoinColumn(name = "person_id", insertable=false, updatable=false)
private Person person;
#ManyToOne
#Getter
#Setter
#JoinColumn(name = "team_id", insertable=false, updatable=false)
private Team team;
#Getter
#Setter
#Enumerated(EnumType.STRING)
private RoleInTeam role;
public enum RoleInTeam {
ADMIN, MEMBER
}
}
#EqualsAndHashCode
#Embeddable
public class PersonToTeamId implements Serializable {
private static final long serialVersionUID = -8450195271351341722L;
#Getter
#Setter
#Column(name = "person_id")
private String personId;
#Getter
#Setter
#Column(name = "team_id")
private String teamId;
}
#Component
public class PersonToTeamIdConverter implements BackendIdConverter {
#Override
public boolean supports(Class<?> delimiter) {
return delimiter.equals(PersonToTeam.class);
}
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
if (id != null) {
PersonToTeamId ptid = new PersonToTeamId();
String[] idParts = id.split("-");
ptid.setPersonId(idParts[0]);
ptid.setTeamId(idParts[1]);
return ptid;
}
return BackendIdConverter.DefaultIdConverter.INSTANCE.fromRequestId(id, entityType);
}
#Override
public String toRequestId(Serializable id, Class<?> entityType) {
if (id instanceof PersonToTeamId) {
PersonToTeamId ptid = (PersonToTeamId) id;
return String.format("%s-%s", ptid.getPersonId(), ptid.getTeamId());
}
return BackendIdConverter.DefaultIdConverter.INSTANCE.toRequestId(id, entityType);
}
}
The problem with this converter is, that the fromRequestId method gets a null as id parameter, when a post request tries to create a new personToTeam association. But there is no other information about the payload of the post. So how should an id with foreign keys to the person and the team be created then? And as a more general question: What is the right approach for dealing many-to-many associations in spring data rest?
After running into the same issue I found a solution. Your code should be fine, except I return new PersonToTeamId() instead of the DefaultIdConverter if id is null in fromRequestId().
Assuming you are using JSON in your post request you have to wrap personId and teamId in an id object:
{
"id": {
"personId": "foo",
"teamId": "bar"
},
...
}
And in cases where a part of the #EmbeddedId is not a simple data type but a foreign key:
{
"id": {
"stringId": "foo",
"foreignKeyId": "http://localhost:8080/path/to/other/resource/1"
},
...
}

HATEOAS RESTFull Entity relation broken

I have a very basic system using Spring with HATEOAS, and I found a problem. I have two very basic entities a car, and a person. Getters and setters avoided to make the question more readable.
#Entity
public class Car implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long carId;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="personId")
private Person owner;
private String color;
private String brand;
}
#Entity
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long personId;
private String firstName;
private String lastName;
#OneToMany(mappedBy="owner")
private List<Car> cars;
}
This are my repositories:
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
List<Person> findByLastName(#Param("name") String name);
}
#RepositoryRestResource(collectionResourceRel = "cars", path = "cars")
public interface CarRepository extends PagingAndSortingRepository<Car, Long> {
List<Person> findByBrand(#Param("brand") String name);
}
I can create and query them, but some reference links are broken. For example, a couple of POSTs successfully creates two related entities:
http://localhost:8080/people
{ "firstName" : "Frodo", "lastName" : "Baggins"}
http://localhost:8080/cars
{ "color":"black","brand":"volvo", "owner":"http://localhost:8080/people/1"}
This are the GET reply on them:
http://localhost:8080/cars/2
{
color: "black2",
brand: "volvo2",
_links: {
self: {
href: "http://localhost:8080/cars/2"
},
owner: {
href: "http://localhost:8080/cars/2/owner"
}
}
}
http://localhost:8080/people/1
{
firstName: "Frodo",
lastName: "Baggins",
_links: {
self: {
href: "http://localhost:8080/people/1"
},
cars: {
href: "http://localhost:8080/people/1/cars"
}
}
}
But I don't know why the owner has this URL on the car:
http://localhost:8080/cars/2/owner
which actually doesn't work.
Any help on that?
It's there because that's what HATEOAS is all about, representing entity/resource relationships with links.
I'm not sure why it doesn't work. I'd guess it probably doesn't work because the owner isn't eagerly fetched when retrieving a car resource.
You can customize which links are generated by following the steps at https://stackoverflow.com/a/24660635/442773

Resources