I have created the following entities.
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(mappedBy = "student")
private List<Book> books;
}
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne
#JoinColumn(name = "STUDENT_ID")
private Student student;
}
My controller looks like this
#RestController
public class Controller {
MyService myService;
public Controller(MyService myService) {
this.myService = myService;
}
#GetMapping("student")
public List<Book> getBooksForStudent(Long id) {
return myService.getBooks(id);
}
}
The service is as follows.
public class MyService {
#Autowired
private StudentRepo studentRepo;
public List<Book> getStudent(Long id) {
Optional<Student> studentOptional = studentRepo.findById(id);
return studentOptional.map(Student::getBooks).orElseThrow(IllegalArgumentException::new);
}
}
I am getting the list of books as expected. But as I'm having lazy loaded list for books I should be getting a LazyInitializationException. I have not added transnational to the method and I'm returning the list of books from the entity itself without mapping it to a DTO. Why is the hibernate session not getting closed after the end of the method?
#RestController is transactional by default. Spring boot automatically registers an OpenEntityManagerInViewInterceptor when you use a web application/you use JPA. Refer #RestController methods seem to be Transactional by default, Why?
Related
I'm trying to make a Spring MVC application.I have 4 entities(Company,Pass_in_trip,Passenger,Trip) Pass_in_trip has 3 keys consisting of Passenger, Trip and Timestamp, I don't know how to properly issue a key and how to transfer it through the jsp page to the controller, and how to issue the controller itself, can anyone tell me?and also an interesting question is how to make a request to the database to search for a record using three keys.
Thanks
here's what I was able to write at the moment, see if there are any errors somewhere
#Entity
#Table(name="company")
public class Company implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name="id_comp")
private int id_comp;
#Column(name="name")
private String name;
//Getters and Setters
#Entity
#Table (name="pass_in_trip")
public class Pass_in_trip implements Serializable {
#EmbeddedId
private KeysPass_in_trip key=new KeysPass_in_trip();
#Column(name="place")
private String place;
//Getters and Setters
#Embeddable
public class KeysPass_in_trip implements Serializable{
#NotNull
#JoinColumn(name="date")
private Timestamp date=new Timestamp(System.currentTimeMillis());
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_psg")
private Passenger id_psg=new Passenger();
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "trip_no" )
private Trip trip_no=new Trip();
//Getters and Setters
//#Override hashCode and equals
#Entity
#Table(name="passenger")
public class Passenger implements Serializable {
#Column(name="name")
private String name;
#NotNull
#Id
#Column(name="id_psg")
#GeneratedValue(strategy = IDENTITY)
private int id_psg;
//Getters and Setters
#Entity
#Table(name="trip")
public class Trip implements Serializable {
#NotNull
#Id
#Column(name="trip_no")
private int trip_no;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "id_comp")
private Company comp=new Company();
#Column(name="plane")
private String plane;
#Column(name="town_from")
private String town_from;
#Column(name="town_to")
private String town_to;
#Column(name="time_out")
private Timestamp time_out;
#Column(name="time_in")
private Timestamp time_in;
//Getters and Setters
Conroller
#Controller
#RequestMapping("/pass_in_trip/")
public class Aero_Controller_Pass_in_trip {
#Autowired
private Aero_DAO service;
public void setService(Aero_DAO service) {
this.service = service;
}
#RequestMapping(method=RequestMethod.GET)
public String list(Model uiModel) {
List <Pass_in_trip> pass_in_trip=service.findallPass_in_trip();
uiModel.addAttribute("pass_in_trip",pass_in_trip);
return "/pass_in_trip/list";
}
#PreAuthorize("hasRole('ROLE_Admin')")
#RequestMapping(value="delete/{id}",method=RequestMethod.GET)
public String delete(#PathVariable("id")int id, Model uiModel) {
if(service.findByIdPass_in_Trip(id)!=null)
service.delete_Pass_in_trip(id);
return "redirect:/pass_in_trip/";
}
#PreAuthorize("hasRole('ROLE_Admin')")
#RequestMapping(value="update/{id}",method=RequestMethod.GET)
public String updateform(#PathVariable("id")int id, Model uiModel) {
System.out.println("upform");
uiModel.addAttribute("pass_in_trip",service.findByIdPass_in_Trip(id));
System.out.println("upform2");
return "/pass_in_trip/edit";
}
#RequestMapping(value="update/0",method=RequestMethod.GET)
public String newform(Model uiModel) {
System.out.println("Привет!");
return "/pass_in_trip/edit";
}
#PreAuthorize("hasRole('ROLE_Admin')")
#RequestMapping(value="update/{id}",method = RequestMethod.POST)
public String update(Pass_in_trip pass_in_trip,BindingResult bindingResult,Model uiModel,HttpServletRequest httprervletrequest , RedirectAttributes redirectatributes) {
if (bindingResult.hasErrors()) {
uiModel.addAttribute("pass_in_trip", pass_in_trip);
return "pass_in_trip/update";}
service.save(pass_in_trip);
return "redirect:/pass_in_trip/";
}
}
List.jsp
interested in this part:
<s:authorize access="hasRole('ROLE_Admin')">
<td> To change </td>
<td> Delete </td>
</s:authorize>
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.
I have a Springboot Application with Repositories having Spring Data JPA Queries like findOne, findAll and also derived ones like findByID or findByName etc.
What I want to achieve is multitenancy. All entities have an "account_id" column which holds the tenant.
How do I add a filter like "account_id" to all the queries metioned above without using derived queries that contains those name slike findIdAndAccountid (which would be findone)
#Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
Category findByName(String name);
}
Here's the corresponding entity
#Entity
#Table(name = "unit")
#Data
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
I know most people use schemas as tenant separation but that's impossible for me. Is there a way (I didn't find one) to add such a tenant filter condition on those queries without writing NamedQueries or using DerivedQueries. An elegeant solution like annotate the repository or entity or maybe the queries that all queries should add the additional filter "account_id"?
You can add Where clause on your Entity classes (Didnt had time to test )
#Entity
#Table(name = "unit")
#Data
#Where(clause = "account_id= :account_id")
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
Update and Solution
1. Create a Filter & FilterDef on the entity like so
#FilterDef(name="accountFilter", parameters=#ParamDef( name="accountId", type="long" ) )
#Filters( {
#Filter(name="accountFilter", condition=":accountId = account_id")
} )
public class Category {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
enable filtering in the controller by autowiring entitymanager, writing a method to enable the filter and activate the filter in #ModelAttribute for each request
#RestController
#RequestMapping(path = "/categories",produces = MediaType.APPLICATION_JSON_VALUE )
public class CategoryController {
private final CategoryRepository repository;
#Autowired
private EntityManager entityManager;
CategoryController(CategoryRepository repository) {
this.repository = repository;
}
private void activateFilter() {
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("accountFilter");
filter.setParameter("accountId", Long.valueOf(TenantContext.getCurrentTenant()));
}
#ModelAttribute
public void initFilter() {
activateFilter();
}
... your rest methods here
}
I've been working with spring boot data jpa + spring + mysql + thymeleaf and have encountered a problem.
Is a #OneToMany (fetch = FetchType.LAZY) relationship for a list, the list loads hibernate like #OneToMany (fetch = FetchType.EAGER) outside. But if I use the annotation #ManyToOne this does work perfectly.
Any idea why it happens this behavior?
By the way, I want to keep the spring.jpa.open-in-view = true property. I debug an application Author - Book (One to Many) developed in IntelliJ IDEA.
Here's the Book class.
#Entity
public class Book implements Serializable {
#Id
#GeneratedValue
private Integer id;
#NotNull
private String name;
#NotNull
#JoinColumn(name = "author_id")
#ManyToOne(fetch = FetchType.LAZY)
private Author author;
/* getters and setters */
}
Here the class Author
#Entity
public class Author implements Serializable {
#Id
#GeneratedValue
private Integer id;
#NotNull
private String name;
#OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
private List<Book> bookList;
/* getters and setteres */
}
The controller for debug.
#Controller
#RequestMapping("/")
public class HomeController {
#Autowired
private AuthorRepository authorRepository;
#Autowired
private BookRepository bookRepository;
#GetMapping
private ModelAndView index() {
List<Author> authorList = authorRepository.findAll();
return new ModelAndView("home"); // first breakpoint
}
#GetMapping("/books")
private ModelAndView viewBooks() {
List<Book> bookList = bookRepository.findAll();
return new ModelAndView("books"); // second breakpoint
}
}
Here's the result.
First breakpoint result
Second breakpoint result
Everything seems to be defined correctly, and after a research, I have done Spring data does use lazy loading plus you defined it on your associations.
I believe that when you use the debugging views in order to tell what inside, then you actually do the fetching.
Can you tell me, why the record is posted twice in the database. I think. this happens because I use save() method. But shouldn't I save the master-entity and dependent-entity separately?
Controller method:
#RequestMapping(value = "/addComment/{topicId}", method = RequestMethod.POST)
public String saveComment(#PathVariable int topicId, #ModelAttribute("newComment")Comment comment, BindingResult result, Model model){
Topic commentedTopic = topicService.findTopicByID(topicId);
commentedTopic.addComment(comment);
// TODO: Add a validator here
if (!comment.isValid() ){
return "//";
}
// Go to the "Show topic" page
commentService.saveComment(comment);
return "redirect:../details/" + topicService.saveTopic(commentedTopic);
}
Services:
#Service
#Transactional
public class CommentService {
#Autowired
private CommentRepository commentRepository;
public int saveComment(Comment comment){
return commentRepository.save(comment).getId();
}
}
#Service
#Transactional
public class TopicService {
#Autowired
private TopicRepository topicRepository;
public int saveTopic(Topic topic){
return topicRepository.save(topic).getId();
}
}
Model:
#Entity
#Table(name = "T_TOPIC")
public class Topic {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#ManyToOne
#JoinColumn(name="USER_ID")
private User author;
#Enumerated(EnumType.STRING)
private Tag topicTag;
private String name;
private String text;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();
}
#Entity
#Table(name = "T_COMMENT")
public class Comment
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#ManyToOne
#JoinColumn(name="TOPIC_ID")
private Topic topic;
#ManyToOne
#JoinColumn(name="USER_ID")
private User author;
private String text;
private Date creationDate;
}
In this concrete case, you do not need to save the master and the client.
Saving the master or the client would be enough (with this concrete mapping)
But I think the main problem is that you do not have a good equals method in your Comment so your ORM Provider think that there are two different comments, and therefore store them twice.