I am new to JPA, and I'm trying to make a datatable form that allows you to press "Remove" to remove a User from a Course. The problem is that only the first (uppermost) from the datatable is clickable, and removes the course from that user. (When it's removed, the next on the list is now the uppermost and working. )
The problem is that the other links (everyone but the uppermost) are refreshing the site without URL parameters (resetting session?), and doesn't invoke the removeUserFromCourse(..) method. If anyone knows why this is happeing, please feel free to answer and point out my mistakes.
Controller (shortened) :
#Model
public class UserController{
private UserDAO persister;
private User user;
// 103 is just a default value
private int selectedID = 103;
#Inject
public UserController(UserDAO persister) {
this.persister = persister;
}
#PostConstruct
public void init() {
this.user = new User();
}
public int getSelectedID() {
return selectedID;
}
public void setSelectedID(int selectedID) {
this.selectedID = selectedID;
}
public void removeUserFromCourse(int courseID){
persister.removeFromCourse(selectedID, courseID);
user = persister.getUser(selectedID);
}
}
Form in edit-user.xhtml:
<h:form>
<h:dataTable id="mdtb" value="#{userController.getUserCourses()}" var="course"
styleClass=" col-lg-6">
<h:column>
<h:outputText value="#{course.name} (#{course.id})" styleClass="col-lg-6"/>
<h:commandLink update="mdtb" value="Remove"
action="#{userController.removeUserFromCourse(course.id)}">
<f:param name="id" value="#{userController.selectedID}"/>
</h:commandLink>
</h:column>
</h:dataTable>
</h:form>
User JPA (shortened):
#Stateless
public class JPAUserDao implements UserDAO {
EntityManagerFactory entityManagerFactory;
#PersistenceContext(name = "Egentreningprosjekt")
EntityManager entityManager;
public JPAUserDao() {
}
public JPAUserDao(EntityManager entityManager){
this.entityManager = entityManager;
}
#Override
public User update(User user) {
System.out.println("updating user " + user);
entityManager.merge(user);
return user;
}
#Override
public void removeFromCourse(int userID, int courseID) {
User user = getUser(userID);
List<Course> courses = user.getCourses();
Course courseToBeDeleted = null;
for(Course course : courses){
if(course.getId() == courseID){
courseToBeDeleted = course;
}
}
if(courseToBeDeleted != null){
courses.remove(courseToBeDeleted);
courseToBeDeleted.getUsers().remove(user);
}
user.setCourses(courses);
update(user);
entityManager.merge(courseToBeDeleted);
}
User.java (shortened):
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Min(value = 0, message = "ID cannot be negative")
private int id;
#NotNull
#Pattern(regexp = "^([A-Z|a-z|0-9](\\.|_){0,1})+[A-Z|a-z|0-9]\\#([A-Z|a-z|0-9])+((\\.){0,1}[A-Z|a-z|0-9]){2}\\.[a-z]{2,3}$")
private String email;
#ManyToMany(mappedBy = "users", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
private List<Course> courses;
// getters/setters
}
Answering this now 4 years later. This was due to 103 being hardcoded, as the comments hinted to.
Related
I'm creating Pet Clinic. If the user wants to add new pet he has to input Owner Id. How to check if this Id already exists. If no I want to use the redirect to the new form where the user can add new Owner.
<tr>
<td><label>Owner: </label></td>
<td><form:input path="ownerId"/></td>
</tr>
Edit:
I want to check if exists in the database. I have two tables in MySQL. Owner and Pet. Owner can have many Pets, Pet can have just one Owner.
Part of Pet Class:
#Entity
#Table(name="pet")
public class Pet {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="type")
private String type;
#Column(name="name")
private String name;
#Column(name="sickness")
private String sickness;
#Column(name="owner_id")
private String ownerId;
...
}
Owner class:
#Entity
#Repository
public class Owner {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="first_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#Column(name="phone_number")
private String phoneNumber;
...
}
Controller
#Controller
#RequestMapping("/pet")
public class PetController {
#Autowired
private PetService petService;
#GetMapping("/list")
public String listPets(Model theModel){
List<Pet> thePets = petService.getPets();
theModel.addAttribute("pets", thePets);
return "list-pets";
}
...
#PostMapping("/savePet")
public String savePet(#ModelAttribute("pet") Pet thePet){
petService.savePet(thePet);
return "redirect:/pet/list";
}
}
Serive
#Service
public class PetServiceImpl implements PetService{
#Autowired
private PetDAO petDAO;
#Transactional
public List<Pet> getPets() {
return petDAO.getPets();
}
#Transactional
#Override
public void savePet(Pet thePet) {
petDAO.savePet(thePet);
}
}
DAO
#Repository
public class PetDAOImpl implements PetDAO {
#Autowired
private SessionFactory sessionFactory;
public List<Pet> getPets() {
Session currentSession = sessionFactory.getCurrentSession();
Query<Pet> theQuery = currentSession.createQuery("from Pet", Pet.class);
List<Pet> thePets = theQuery.getResultList();
return thePets;
}
#Override
public void savePet(Pet thePet) {
Session currentSession = sessionFactory.getCurrentSession();
currentSession.saveOrUpdate(thePet);
}
}
In JSP you can check if object has value with JSTL core lib:
<c:if test="${not empty pet.ownerId}">...</c:if>
where pet is your model object (passed to form).
I'm a novice java developer and now develop User Management application using Spring-Hibernate. I have two entities User and Email. And User entity has a field Email which is mapped to Email entity as #ManyToOne. Any Email can be used by multiple users.
When I save a new User in DB for every new user I get a new row Email, even if the same record is already in the Email Table. How to properly make save operation to avoid duplication of the same records in the table Email?
User.java
#Entity
#Table(name = "USER")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="ID")
private Long id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumn(name = "email_id")
private Email email;
public User(){
}
public Email getEmail() {
return email;
}
public void setEmail(Email email) {
this.email = email;
}
...
}
Email.java
#Entity
#Table(name = "EMAIL")
public class Email implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="ID")
private Long id;
#Column(name = "emailaddress")
private String emailaddress;
#OneToMany (mappedBy = "email", targetEntity=User.class)
private Set<User> user= new HashSet<User>();
public Email() {
}
public Email(String emailaddress) {
this.emailaddress = emailaddress;
}
public String getEmailaddress() {
return emailaddress;
}
public void setEmailaddress(String emailaddress) {
this.emailaddress = emailaddress;
}
...
}
Controller.java
#Transactional
#RequestMapping(value = "/adduser", method = RequestMethod.POST)
public String saveOrder(#ModelAttribute("user") User user, BindingResult result, #RequestParam String action){
emailDAO.create(user.getEmail());
userDAO.create(user);
return "index";
...
}
EmailDAO.java
#Transactional
#Repository
public class EmailDAO{
#Autowired
private SessionFactory sessionFactory;
public Email create(Email email) {
sessionFactory.getCurrentSession().save(email);
return email;
}
}
UserDAO.java
#Transactional
#Repository
public class UserDAO{
#Autowired
private SessionFactory sessionFactory;
public User create(User user) {
sessionFactory.getCurrentSession().save(user);
return user;
}
}
webform.jsp
<form:form action="${formUrl}" method="post" modelAttribute="user">
<form:label path="name" for="appname">username</form:label>
<form:input path="name" id= "appname" cssClass="form-control"/>
<form:label path="email.emailaddress" for="appemail">Email</form:label>
<form:input path="email.emailaddress" id= "appemail"/>
<button type="submit" name="action" value="Add">Save</button>
</form:form>
database diagram
Example of the DB records
That is because you keep on saving the Email as a new Record
#Transactional
#RequestMapping(value = "/adduser", method = RequestMethod.POST)
public String saveOrder(#ModelAttribute("user") User user, BindingResult result, #RequestParam String action){
emailDAO.create(user.getEmail()); // Inserting Email as New Record
userDAO.create(user);
return "index";
...
}
And you don't have unique=true on Email Entity
#Column(name = "emailaddress", unique = true)
private String emailaddress;
Which you should ideally have so that there will be no duplicate Emails will get inserted even by accidentally.
You need to modify EmailDAO
#Transactional
#Repository
public class EmailDAO{
#Autowired
private SessionFactory sessionFactory;
public Email create(Email email) {
sessionFactory.getCurrentSession().save(email);
return email;
}
public Email getEmail(String inputEmail) {
Email email = null;
Query query = sessionFactory.getCurrentSession().createQuery("FROM Email e WHERE e.emailaddress = :email");
query.setString("email", inputEmail);
List emails = query.list();
if(emails != null && emails.size() > 0) {
email = (Email)emails.get(0);
} else {
email = new Email();
email.setEmailAddress(inputEmail);
}
return email;
}
}
And you getEmail
#Transactional
#RequestMapping(value = "/adduser", method = RequestMethod.POST)
public String saveOrder(#ModelAttribute("user") User user, BindingResult result, #RequestParam String action){
user.setEmail(emailDAO.getEmail(user.getEmail().getEmailAddress())); // Inserting Email as New Record
userDAO.create(user);
return "index";
...
}
Have to beans:
#Entity
#Table(name="book")
public class Book {
#Id
#Column(name="id_book")
#GeneratedValue(generator="increment")
#GenericGenerator(name="increment", strategy="increment")
private int id;
#Column
#Size(min=1,max=100)
private String title;
#Column
#Size(min=1,max=400)
private String description;
#Column
private Integer year=0;
#ManyToMany(cascade={CascadeType.ALL},fetch = FetchType.EAGER)
#Fetch (FetchMode.SELECT)
#JoinTable(name="book_author",
joinColumns={#JoinColumn(name="book_id_book")},
inverseJoinColumns= {#JoinColumn(name="author_id_author")})
private List<Author> author=new ArrayList<Author>();
//getters/setters
}
and:
#Entity
#Table(name="author")
public class Author {
#Id
#Column(name="id_author")
#GeneratedValue
private Integer id;
#Column
private String name;
#Column
private String surname;
#ManyToMany(mappedBy="author")
private Set<Book> book=new HashSet<Book>();
//getters/setters
}
In my jsp I'm have form for enter data about book, and multiple list for select author(s) from DB, problem only in select authors, therefore give only this code:
<sf:select multiple="true" path="author" items="${authors}" size="7" >
</sf:select>
Where ${authors} - List with objects Author from DB. Use POST request.
In my controller for this page have this (I know it's not correct):
#RequestMapping(value="/addbook", method=RequestMethod.POST)
public String addBook(Book book){
hibarnateService.saveBook(book);
return "redirect:/books";
}
When I'm create book without select authors, but enter another information, all fine, book save in DB. When select some authors get this - The request sent by the client was syntactically incorrect.
Problem solved by add in controller:
#InitBinder
protected void initBinder(WebDataBinder binder){
binder.registerCustomEditor(Author.class, new Editor(hibarnateService));
}
and create class:
public class Editor extends PropertyEditorSupport {
private final Dao hibernateService;
public Editor(Dao hibernateService){
this.hibernateService=hibernateService;
}
#Override
public void setAsText(String text) throws IllegalArgumentException{
Author author=hibernateService.getAuthor(Integer.parseInt(text));
setValue(author);
}
}
P.S. What wrong with me? I can't find the right answer myself until I ask here)
You will need to implement initBinder in your controller, below can be tentative code (not tested)
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(List.class, "authors ", new CustomCollectionEditor(List.class)
{
#Override
protected Object convertElement(Object element)
{
Long id = null;
if(element instanceof Long) {
//From the database 'element' will be a Long
id = (Long) element;
}
return id != null ? authorService.loadAuthorById(id) : null;
}
});
}
My class Categorie needs to have multiple Products, i need to show the products when you click on a category in the jsp... I tried it like this:
#Entity
public class Categorie {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int categorieId;
private String categorieName;
#OneToMany()
#JoinColumn(name = "CategorieNr")
private Set<Product> products;
My Product class:
#Entity
#Table(name = "CentricProduct")
public class Product implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int productId;
private int calories, productRow;
private String description;
private double price;
#Override
public boolean equals(Object object) {
if (!(object instanceof Product)) {
return false;
}
Product other = (Product) object;
return description.equals(other.description);
}
#Override
public int hashCode() {
return description.hashCode();
}
This is the repository i use to get the categorys with their products:
#Repository
public class CategoryRepository implements ICategoryRepository
{
#PersistenceContext
private EntityManager em;
public CategoryRepository() {
}
public CategoryRepository(EntityManager em)
{
this.em = em;
}
public void setEntityManager(EntityManager e)
{
this.em = e;
}
#Transactional(readOnly = true)
#Override
public List<Categorie> findAll()
{
CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
cq.select(cq.from(Categorie.class));
return em.createQuery(cq).getResultList();
}
in my jsp:
<h1 class="listtitle">Productenlijst</h1>
<div id="leftlist" class="list" >
<c:forEach items="${products}" var="item" >
<form:form method="POST" action="shoppinglist.htm" modelAttribute="products">
<input type="submit" value= "${item.categorieName}" class="productlistbtn" alt="Submit" input path="${item}" >
</form:form>
</c:forEach>
</div>
your class Product needs to be declared as #Entity. (you can't embed a one-to-many relationship, especially because you're Productcontains it's own ID)
I think i'm missing something fundamental about how Hibernate works, specifically with lazy loading. My problem is debugging, as I'm not sure if this is a Hibernate problem or a Spring problem in disguise. I thought I would ask here before doing some major refactoring.
I have two Entities. One holds a collection of the other in a OneToMany relationship. For my web page I wish to grab all of the first entity, and subsequently grab the set of associated entities for each and display them.
I believe my problem is this: I use a JpaTemplate to find all entities. This works fine, however because of Lazy loading I do not get the associated set of related entities. In my view (jsp) I want access to this set, but of course it is null because it is being lazy loaded. Now, i'm getting a LazyInitialization exception stating that the transaction has ended. To me this makes sense, of course the transaction should be over by now. The thing is, how can the assoicated set ever be lazy loaded if the transaction is over?
Entity Classes:
#Entity
public class LearningEntry implements Serializable {
private Long id;
String imagePath = "";
Set<Sample> samples = null;
//------------------------------
// Constructors
//------------------------------
public LearningEntry(){
imagePath = "";
samples = new HashSet<Sample>();
}
//------------------------------
// Instance Methods
//------------------------------
public void addSample(Sample s){
samples.add(s);
}
public void removeSample(Sample s){
samples.remove(s);
}
//------------------------------
// Setters and Getters
//------------------------------
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
//#Column(name = "wisi_LE_IMAGEPATH", length = 100, nullable = false)
public String getImagePath() {
return imagePath;
}
public void setImagePath(String imagePath) {
this.imagePath = imagePath;
}
// TODO - ONly works with fetch type EAGER
//#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
public Set<Sample> getSamples() {
return samples;
}
public void setSamples(Set<Sample> samples) {
this.samples = samples;
}
}
Sample Entity
#Entity
public class Sample implements Serializable {
private Long id;
Date creationDate;
String audioFileLocation;
Integer votes;
String description;
public Sample(){
creationDate = new Date();
audioFileLocation = "";
votes = 0;
description = "";
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAudioFileLocation() {
return audioFileLocation;
}
public void setAudioFileLocation(String audioFileLocation) {
this.audioFileLocation = audioFileLocation;
}
#Temporal(TemporalType.DATE)
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getVotes() {
return votes;
}
public void setVotes(Integer votes) {
this.votes = votes;
}
}
DAO Classes:
LearningEntryDAO
#Transactional
public class JpaLearningEntryDAO implements LearningEntryDAO{
private JpaTemplate jpaTemplate;
public JpaLearningEntryDAO(){
}
public void setJpaTemplate(JpaTemplate jpaTemplate){
this.jpaTemplate = jpaTemplate;
}
#Override
//#Transactional
public void delete(Long leId) {
LearningEntry dp = jpaTemplate.find(LearningEntry.class, leId);
jpaTemplate.remove(dp);
}
#Override
#SuppressWarnings("unchecked")
//#Transactional
public List<LearningEntry> findAll() {
return jpaTemplate.find("from LearningEntry");
}
#Override
//#Transactional
public LearningEntry findById(Long leId) {
return jpaTemplate.find(LearningEntry.class, leId);
}
#Override
//#Transactional
public LearningEntry store(LearningEntry dp) {
return jpaTemplate.merge(dp);
}
#Override
#SuppressWarnings("unchecked")
//#Transactional
public void deleteAll(){
throw new RuntimeException("deleteAll not implemented");
}
}
Sample DAO
#Transactional
public class JpaSampleDAO implements SampleDAO{
private JpaTemplate jpaTemplate;
public JpaSampleDAO(){}
public void setJpaTemplate(JpaTemplate jpaTemplate){
this.jpaTemplate = jpaTemplate;
}
#Override
//#Transactional
public void delete(Long sampleId) {
Sample dp = jpaTemplate.find(Sample.class, sampleId);
jpaTemplate.remove(dp);
}
#Override
#SuppressWarnings("unchecked")
public List<Sample> findAll() {
return jpaTemplate.find("from Sample");
}
#Override
public Sample findById(Long sampleId) {
return jpaTemplate.find(Sample.class, sampleId);
}
#Override
public Sample store(Sample dp) {
return jpaTemplate.merge(dp);
}
#Override
#SuppressWarnings("unchecked")
public void deleteAll(){
throw new RuntimeException("deleteAll not implemented");
}
}
Controller
#RequestMapping(value = "/index.htm", method = RequestMethod.GET)
public ModelAndView sayHello(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
Map<String, Object> model = new HashMap<String, Object>();
List<LearningEntry> le = learningEntryService.getLearningEntries();
model.put("learningEntries", le);
return new ModelAndView("main", model);
}
View
<section id="content" class="body">
<ol id="posts-list" class="hfeed">
<c:forEach items="${learningEntries}" var="learningEntry">
<li>
<table class="wisiEntry">
<tr>
<td class="pictureCell">
<img class="wisiEntry-pic" src="${learningEntry.imagePath}" />
</td>
<td class="previousNextCell"
<div class="wisiEntry-nextSampleButton">Next</div>
<div class="wisiEntry-previousSampleButton">Previous</div>
<br />
<div class="wisiEntry-addTagButton">Tag</div>
<div class="wisiEntry-addCommentButton">Comment</div>
<br />
<div class="wisiEntry-uploadButton">Upload</div>
</td>
<td>
<!-- ERROR HAPPENS HERE. Samples should not be null -->
<c:forEach items="${learningEntry.samples}" var="sample" varStatus = "status">
<table class="sampleEntry" ${status.first ? '' : 'style = "display:none"'}>
<tr>
<td class="sampleCell">
<p class="description">
${sample.description}
</p>
<audio src="${sample.audioFileLocation}" controls>
Your browser does not support the <code>audio</code> element.
</audio>
</td>
<td class="voteCell">
<img class="upVote" src="/images/upArrow.jpeg" />
<span class="voteNumber">${sample.votes}</span>
<img class="downVote" src="/images/downArrow.jpeg" />
</td>
</tr>
</table>
</c:forEach>
</td>
</tr>
</table>
</li>
</c:forEach>
</ol><!-- /#posts-list -->
</section><!-- /#content -->
I hope you are using findAll() method down the call. You can load all the associated samples by modifying your method like below.
public List<LearningEntry> findAll() {
List<LearningEntry> entries = jpaTemplate.find("from LearningEntry");
for(LearningEntry entry : entries){
entry.getSamples().size();
}
return entries;
}
Or, as you already know, you can also achieve this by changing fetch to FetchType.EAGER. But this might not suit you in all cases. Therefore, former way is better.
Or you might like to do no change anywhere, and define another method to get all the samples based on LearningEntry, this way you will be able to fire up an AJAX call on some event. But that might not suit here in this case.
Thanks to Vinegar for providing a working answer (upvoted).
I decided to add this answer that has also worked for me. I took this approach because I may want to make separate ajax calls in the future. In other words, I can ask for the LearningEntry in one transaction, than ask for its samples some time down the road.
#Transactional
public Set<Sample> getSamplesForLearningEntry(LearningEntry le) {
// Reload the le from the database so it is not transient:
LearningEntry le = leDAO.store(le);
le.getSamples.size();
return le.getSamples();
}
Most frameworks offer the 'open session in view' pattern. See https://www.hibernate.org/43.html:
The solution, in two-tiered systems,
with the action execution, data access
through the Session, and the rendering
of the view all in the same virtual
machine, is to keep the Session open
until the view has been rendered.
For data that is read often and hardly ever updated, query caching can help too. This reduces the load on the database, but increases memory usage. Hibernate can be configured to do this for you.