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.
Related
I know English badly, but i'm trying to describe my problem.
I'm new in Spring. And I have some problems with adding data to my database.I have to table Pc and Pc characteristics. They are related by Id. It's easy to add data in non realted table, but how can I add data in related table? What shoud I write in my Controller? There are some classes below.
Pc class:
#Entity
#Table(name = "pc")
public class Pc {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private int price;
public Pc(){}
public Pc(String name, int price) {
this.name = name;
this.price = price;
}
#OneToMany
#JoinColumn(name = "pc_id")
private List<PcChars> chars = new ArrayList<>();
public List<PcChars> getChars() {
return chars;
}
public void setChars(List<PcChars> chars) {
this.chars = chars;
}
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;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
PcChars class:
#Entity
#Table(name = "pcChars")
public class PcChars {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private String value;
public PcChars(){}
public PcChars(String name, String value) {
this.name = name;
this.value = value;
}
#ManyToOne
private Pc pc;
public Pc getPc() {
return pc;
}
public void setPc(Pc pc) {
this.pc = pc;
}
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;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
PcCharactsController:
#Controller
public class PcCharactsController {
final private PcRepo pcRepo;
final private PcCharRepo pcCharRepo;
public PcCharactsController(PcRepo pcRepo, PcCharRepo pcCharRepo) {
this.pcRepo = pcRepo;
this.pcCharRepo = pcCharRepo;
}
//Pc characteristics list
#GetMapping("pc/{id}/")
public String pcCharList(#PathVariable int id, Model model) throws Exception{
Pc pc = pcRepo.findById(id).orElseThrow(() -> new Exception("PostId " +
id + " not found"));
List<PcChars> pcChars = pc.getChars();
model.addAttribute("model", pc.getName());
model.addAttribute("pcChars", pcChars);
return "charList";
}
//add characteristic
#PostMapping("pc/{id}/")
public String addCharact(){
return "charList";
}
Characteristics.ftl:
<html>
<head>
<title>Ho</title>
</head>
<body>
<div>
<form method="post" action="/pc/${id}/">
<input type="text" name="name">
<input type="text" value="value">
<input type="hidden" name="pc_id" value="${id}">
<button type="submit">Add</button>
</form>
</div>
</body>
</html>
Since you are not using any modelAttribute to bind the input values straight to a POJO you can use simple HttpServletRequest to get the input attributes, use them to create the object you want to store and store it using Hibernate
#PostMapping("pc/{id}/")
public String addCharact(HttpServletRequest req){
String name = req.getParameter("name");
String value = req.getParameter("value");
String id = req.getParameter("id");
PcChars pcchars = new PcChars(name,value,id); // create the corresponding constructor
SessionFactory sessionFactory;
Session session = sessionFactory.openSession();
Transaction tx = null;
try{
tx = session.getTransaction();
tx.begin();
session.save(pcchars);
tx.commit();
}
catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
} finally {
session.close();
}
return "charList";
}
The part of Spring you're using is called Spring data, a library that allows you to use JPA in your Spring application. JPA is a specification for frameworks called ORM (Object-relationnal mapping).
To keep it simple, in your code, you do not use the relational approach anymore, but an object approach. Annotations you put on your classes' fields are used to define mappings between them and your database tables and fields.
So, you don't have to insert both entities separately anymore. You need to create a Pc instance, then to create a PcChars one, and finally to add the chars into the pc's chars list, like this :
Pc myPc = new Pc();
PcChars myChars = new PcChars();
myPc.getChars().add(myChars);
And when you'll use your repository to save the modifications with this :
pcRepo.save(myPc);
The JPA implementation will automatically do the work for you :
Inserting the row corresponding to your PC instance in the PC table
Inserting the row corresponding to your PC chars in the the PC_CHARS table
Settings the PC_CHARS.PC_ID with the ID of the freshly inserted PC instance's id in order to create the reference between them.
Not sure, but I think the ORM also do this when you add your chars to the pc instance :
myChars.setPc(myPc);
in order to make the bound between both instances reciprocal.
Note that I used arbitrary field names according to your schema.
I strongly suggest you to give responsibility of relationship to child side when using #OneToMany relation.
Modify your parent class as below:
#OneToMany(cascade = CascadeType.ALL, mappedBy="pc")
#BatchSize(size = 10)
private List<PcChars> chars = new ArrayList<>();
public void addPcChar(PcChar pcChar) {
this.chars.add(pcChar);
pcChar.setPc(this);
}
On the child class:
#ManyToOne
#JoinColumn(name = "pc_id")
private Pc pc;
Now you can persist your parent with child as below :
Pc pc = new Pc();
PcChar pcChar = new PcChar();
pc.addPcChar(pcChar);
If you use spring boot data repository, it saves it correctly as below
// assume your repository like below
public interface PcRepository extends CrudRepository<Pc, Integer> {}
// in your service or whatever in some place
pcRepository.save(pc);
With saving hibernate entity manager:
EntityManagerFactory emfactory =
Persistence.createEntityManagerFactory("Hibernate");
EntityManager entitymanager = emfactory.createEntityManager();
entitymanager.getTransaction().begin();
entitymanager.persist(pc);
entitymanager.getTransaction().commit();
entitymanager.close();
emfactory.close();
For detailed information about hibernate relationship take a look at my post : https://medium.com/#mstrYoda/hibernate-deep-dive-relations-lazy-loading-n-1-problem-common-mistakes-aff1fa390446
Good afternoon,
I am newbie to Spring MVC. I'm stuck with the following error while running my project "The request sent by the client was syntactically incorrect."
My project has two entities, Team and Country which have a ManyToOne relationship. Both these entities map tables created in mysql database.
I started the project with only the Team entity, and sucessfuly created my classes (DAO, controller, services, etc) and jsp to create new teams.
Now, I created the class Country to relate both entities and I added a dropdown list in the "add-team-form.jsp" to select the country of the new team. This page is correctly displayed (all countries appear in the dropdown list), however, when I click "submit" to create the new team, I get the error "The request sent by the client was syntactically incorrect."
Can you please help me to identify my error? I'm guessing it's in the "add-team-form.jsp".
1 - Entity Team:
#Entity
#Table(name="teams")
public class Team implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "name", length = 40, nullable = false)
private String name;
#Column(name = "rating", length = 6, nullable = false)
private Integer rating;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "id_country", nullable = false)
private Country country;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getRating() {
return rating;
}
public void setRating(Integer rating) {
this.rating = rating;
}
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
}
2 - Entity Country:
#Entity
#Table(name = "countries")
public class Country implements Serializable{
#Id
#Column(name= "id_country", length = 6)
private String idCountry;
#Column(name = "name", length = 255, nullable = false)
private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "country")
private List<Team> teams;
public String getIdCountry() {
return idCountry;
}
public void setIdCountry(String idCountry) {
this.idCountry = idCountry;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
My Team DAO
#Repository
public class TeamDAOImpl implements TeamDAO {
#Autowired
private SessionFactory sessionFactory;
private Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
#Override
public void addTeam(Team team) {
getCurrentSession().save(team);
}
}
My Team Service
#Service
#Transactional
public class TeamServiceImpl implements TeamService {
#Autowired
private TeamDAO teamDAO;
public void addTeam(Team team) {
teamDAO.addTeam(team);
}
My Team Controller
#Controller
#RequestMapping(value="/team")
public class TeamController {
#Autowired
private TeamService teamService;
#Autowired
private FilterService filterService;
#RequestMapping(value="/add", method=RequestMethod.GET)
public ModelAndView addTeamPage() {
ModelAndView modelAndView = new ModelAndView("add-team-form");
modelAndView.addObject("team", new Team());
return modelAndView;
}
#RequestMapping(value="/add", method=RequestMethod.POST)
public ModelAndView addingTeam(#ModelAttribute Team team) {
ModelAndView modelAndView = new ModelAndView("home");
teamService.addTeam(team);
String message = "Team was successfully added.";
modelAndView.addObject("message", message);
return modelAndView;
}
#ModelAttribute("countryList")
public Map<String, String> getCountryList(){
Map<String, String> countryList = filterService.getCountries();
return countryList;
}
...
}
My "add-team-form.jsp"
<%#taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<?xml version="1.0" encoding="ISO-8859-1" ?>
<%# page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Add team page</title>
</head>
<body>
<h1>Add team page</h1>
<form:form method="POST"
modelAttribute="team"
action="${pageContext.request.contextPath}/team/add.html">
<table>
<tbody>
<tr>
<td>Name:</td>
<td><form:input path="name" /></td>
</tr>
<tr>
<td>Rating:</td>
<td><form:input path="rating" /></td>
</tr>
<tr>
<td><label>Country</label></td>
<td>
<form:select path="country.idCountry">
<form:options items="${countryList}" />
</form:select>
</td>
<tr>
<td><input type="submit" value="Add" /></td>
<td></td>
</tr>
</tbody>
</table>
</form:form>
</body>
</html>
There is no error showing in the console of eclipse, but here is the error im receiving from the browser:
HTTP Status 400 -
type Status report
message
description The request sent by the client was syntactically incorrect.
Apache Tomcat/7.0.47
There's a couple of problems I can see here - you are posting to add/team/add.html and not hitting your post handler. You don't need the action attribute as you're posting to the same endpoint;
<form:form method="POST" modelAttribute="team" >
Secondly your are injecting the countries as a map, so these are ID/display values which works great for key/value pairs and for binding a value to a string field. In this case, Spring is trying to bind your country ID (String) to the team.country(Country) field which will fail. To help Spring out you need a databinder; in your controller add;
#InitBinder
public void initBinder (WebDataBinder binder) {
binder.registerCustomEditor(Country.class, new CountryEditor());
}
and create the property editor class;
public class CountryEditor extends PropertyEditorSupport {
#Override
public void setValue(Object value) {
super.setValue(value);
}
public String getAsText() {
if (getValue() == null) return null;
return ((Country) getValue()).getName();
};
public void setAsText(String text) throws IllegalArgumentException {
if (text != null) {
Country country = // something like filterService.getCountryById(text);
setValue(country);
}
};
}
There's more information in the Spring documentation
The error you are receiving generally happens if a parameter is missing or is in a different format and cannot be converted to the expected type.Check the values being passed to the Team object.You can either log the request and response or set the log level to "DEBUG",this will display the exact error in logs.
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;
}
});
}
In a spring mvc application that uses hibernate an jpa, I have a module for editing phone numbers which is not committing changes to the database when the user enters altered information for phone number and phone number type. I keep going over the code, but I cannot see where the problem is. Can someone show me what to change in the below?
Here are the relevant methods of PhoneNumberController.java:
#RequestMapping(value = "/patients/{patientId}/phonenumbers/{phonenumberId}/edit", method = RequestMethod.GET)
public String initUpdateForm(#PathVariable("phonenumberId") int phonenumberId, Map<String, Object> model) {
System.out.println("--------------------------------- made it into initUpdateForm() method");
PhoneNumber phonenumber = this.clinicService.findPhoneNumberById(phonenumberId);
model.put("phonenumber", phonenumber);
return "phonenumbers/createOrUpdatePhoneNumberForm";
}
#RequestMapping(value = "/patients/{patientId}/phonenumbers/{phonenumberId}/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public String processUpdateForm(#ModelAttribute("phonenumber") PhoneNumber phonenumber, BindingResult result, SessionStatus status) {
// we're not using #Valid annotation here because it is easier to define such validation rule in Java
new PhoneNumberValidator().validate(phonenumber, result);
if (result.hasErrors()) {return "phonenumbers/createOrUpdatePhoneNumberForm";}
else {
this.clinicService.savePhoneNumber(phonenumber);
status.setComplete();
return "redirect:/patients?patientID={patientId}&type=phone";
}
}
Here is the PhoneNumber.java model:
#Entity
#Table(name = "patient_phone_numbers")
public class PhoneNumber {
#Id
#GeneratedValue
#Column(name="id")
private Integer id;
#ManyToOne
#JoinColumn(name = "client_id")
private Patient patient;
#Column(name="phonenumber")
private String number;
#ManyToOne
#JoinColumn(name = "type_id")
private PhoneNumberType type;
#Column(name = "preferred")
private boolean preferred;
#Column(name = "okmessages")
private boolean okmessages;
public Integer getId(){return id;}
public void setId(Integer i){id=i;}
protected void setPatient(Patient patient) {this.patient = patient;}
public Patient getPatient(){return this.patient;}
public String getNumber(){return number;}
public void setNumber(String pn){number=pn;}
public PhoneNumberType getType(){return this.type;}
public void setType(PhoneNumberType nt){this.type=nt;}
public boolean getPreferred(){return preferred;}
public void setPreferred(boolean p){preferred=p;}
public boolean getOkmessages(){return okmessages;}
public void setOkmessages(boolean m){okmessages=m;}
public boolean isNew() {return (this.id == null);}
}
And here is the createOrUpdatePhoneNumberForm.jsp:
<html lang="en">
<jsp:include page="../fragments/headTag.jsp"/>
<body>
<div class="container">
<jsp:include page="../fragments/bodyHeader.jsp"/>
<c:choose>
<c:when test="${phonenumber['new']}">
<c:set var="method" value="post"/>
</c:when>
<c:otherwise>
<c:set var="method" value="put"/>
</c:otherwise>
</c:choose>
<h2>
<c:if test="${phonenumber['new']}">New </c:if>
Phone Number
</h2>
<form:form modelAttribute="phonenumber" method="${method}" class="form-horizontal">
<div class="control-group" id="patient">
<label class="control-label">Patient </label>
<c:out value="${phonenumber.patient.firstName} ${phonenumber.patient.lastName}"/>
</div>
<petclinic:inputField label="PhoneNumber" name="number"/>
<div class="control-group">
<petclinic:selectField name="type" label="Type" names="${numtypes}" size="5"/>
</div>
Preferred number? <form:checkbox path="preferred"/><br>
OK to leave messages? <form:checkbox path="okmessages"/>
<td>
</td>
<div class="form-actions">
<c:choose>
<c:when test="${phonenumber['new']}">
<button type="submit">Add Phone Number</button>
</c:when>
<c:otherwise>
<button type="submit">Update Phone Number</button> <h3> Link to delete will go here.</h3>
</c:otherwise>
</c:choose>
</div>
</form:form>
<c:if test="${!phonenumber['new']}">
</c:if>
</div>
</body>
</html>
ClinicService.java is:
#Service
public class ClinicServiceImpl implements ClinicService {
private DocumentRepository documentRepository;
private PatientRepository patientRepository;
private AddressRepository addressRepository;
private PhoneNumberRepository phoneNumberRepository;
#Autowired
public ClinicServiceImpl(DocumentRepository documentRepository, PatientRepository patientRepository, AddressRepository addressRepository, PhoneNumberRepository phoneNumberRepository) {
this.documentRepository = documentRepository;
this.patientRepository = patientRepository;
this.addressRepository = addressRepository;
this.phoneNumberRepository = phoneNumberRepository;
}
#Override
#Transactional(readOnly = true)
public Collection<DocumentType> findDocumentTypes() throws DataAccessException {return documentRepository.findDocumentTypes();}
#Override
#Transactional(readOnly = true)
public Collection<Gender> findGenders() throws DataAccessException {return patientRepository.findGenders();}
#Override
#Transactional(readOnly = true)
public Collection<Race> findRaces() throws DataAccessException {return patientRepository.findRaces();}
#Override
#Transactional(readOnly = true)
public Patient findPatientById(int id) throws DataAccessException {return patientRepository.findById(id);}
#Override
#Transactional(readOnly = true)
public Collection<Patient> findPatientByLastName(String lastName) throws DataAccessException {return patientRepository.findByLastName(lastName);}
#Override
#Transactional
public void savePatient(Patient patient) throws DataAccessException {
System.out.println("-------------------------------------- inside clinicservice.savePatient()");
patientRepository.save(patient);}
#Override
#Transactional(readOnly = true)
public Document findDocumentById(int id) throws DataAccessException {
System.out.println("--------------- made it into clinicservice.findDocumentById() method");
return documentRepository.findById(id);}
#Override
#Transactional
public void saveDocument(Document doc) throws DataAccessException {documentRepository.save(doc);}
#Override
#Transactional
public void saveAddress(Address addr) throws DataAccessException {addressRepository.save(addr);}
#Override
#Transactional(readOnly=true)
public Address findAddressById(int id) throws DataAccessException {return addressRepository.findById(id);}
#Override
#Transactional(readOnly = true)
public Collection<State> findStates() throws DataAccessException {return addressRepository.findStates();}
#Override
#Transactional(readOnly = true)
public Collection<PhoneNumberType> findPhoneNumberTypes() throws DataAccessException {return phoneNumberRepository.findPhoneNumberTypes();}
#Override
#Transactional(readOnly = true)
public void savePhoneNumber(PhoneNumber pn) throws DataAccessException {
System.out.println("++++++++++++++++++++ inside savePhoneNumber(pn) : "+pn.getNumber()+" , "+pn.getType().getName());
phoneNumberRepository.save(pn);
}
#Override
#Transactional(readOnly=true)
public PhoneNumber findPhoneNumberById(int id) throws DataAccessException {return phoneNumberRepository.findById(id);}
}
JpaPhoneNumberRepository.java contains the following:
#PersistenceContext
private EntityManager em;
#Override
public void save(PhoneNumber phonenumber) {
System.out.println("------------------------------ inside save(phonenumber) : "+phonenumber.getNumber()+" , "+phonenumber.getType().getName());
if (phonenumber.getId() == null) {
System.out.println("phonenumber.getId() == null ");
this.em.persist(phonenumber);
}
else {
System.out.println("else");
this.em.merge(phonenumber);}
}
The correct new values for getNumber() and getType().getName() print out. And "else" prints out, but then the data is not updated in the database. Why not? (note that clinicservice calls this save() method of JpaPhoneNumberRepository.java.)
The problem is that you have ClinicServiceImpl > savePhoneNumber method annotated as #Transactional(readOnly = true). Change it to #Transactional
Why is the savePhoneNumber method in your ClinicService.java as #Transactional(readOnly=True)?
That is the cause of the problem
I am trying to create simple blog with jsf 2.0. It contains 2 classes:
BlogManager (in session scope)
Blog(in request scope)
#Named("blogManager")
#SessionScoped
public class BlogManager {
private List<Blog> blogs;
#ManagedProperty(value = "#{blog}")
Blog blog;
public BlogManager() {
blogs = new ArrayList<Blog>();
}
public List<Blog> getBlogs() {
return blogs;
}
public void setBlogs(List<Blog> blogs) {
this.blogs = blogs;
}
public Blog getBlog() {
return blog;
}
public void setBlog(Blog blog) {
this.blog = blog;
}
public void addBlog() {
blogs.add(blog);
}
public void removeBlog(Blog blog) {
blogs.add(blog);
}
}
the blog class:
#Named("blog")
#RequestScoped
public class Blog {
private Integer id;
private String title;
private String text;
private Date date;
public Blog() {
id = (int) System.currentTimeMillis();
}
private Integer readed;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Integer getReaded() {
return readed;
}
public void setReaded(Integer readed) {
this.readed = readed;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
then inside my xhtml form i am writing this thing:
<h:form>
<fieldset>
<legend>Blog</legend>
<p>
Title:
<h:inputText value="#{blog.title}" />
</p>
<p>
Text:
<h:inputTextarea value="#{blog.text}" />
</p>
<p>
<h:commandButton action="#{blogManager.addBlog}" />
</p>
</fieldset>
</h:form>
when i click submit it says blog target unreachable. It is request scope. I was thinking once jsf could not find the blog object then it creates and assigns user input to blog object. then i can see it in blogManager. But it does not work. what scope should i use for Blog class? And can i inject request scope in blogManager?
thanks
You are mixing JSF and CDI annotations. You have managed your beans by CDI's #Named, but yet you're trying to use JSF specific #ManagedProperty annotation to inject one CDI managed bean in another CDI managed bean. This is not going to work. You need to use CDI's own #Inject annotation instead. The #ManagedProperty works only with JSF #ManagedBean classes.
Fix it accordingly:
#Inject
private Blog blog;
Further you also need to make absolutely sure that when you manage beans by CDI's #Named, that all the scope annotations like #SessionScoped are coming from the javax.enterprise.context package and not from the javax.faces.bean package.
Unrelated to the concrete problem, this design is flawy. The Blog class should not be a managed bean, but an #Entity and be just a normal property of the BlogManager class.
Ideally, your Blog class should only be a POJO. And it should be a member of BlogManager. So the only bean you need will be
BlogManager :
#Named("blogManager")
#SessionScoped
public class BlogManager {
private List<Blog> blogs;
private Blog blog;
public BlogManager() {
blogs = new ArrayList<Blog>();
blog = new Blog();
}
// getter setters for blog and blogs
public void addBlog() {
blogs.add(blog);
}
public void removeBlog(Blog blog) {
blogs.add(blog);
}
}
the blog class:
public class Blog {
private Integer id;
private String title;
private String text;
private Date date;
private Integer readed;
public Blog() {
id = (int) System.currentTimeMillis();
}
// getters-setters
}
And your xhtml :
<h:form>
<fieldset>
<legend>Blog</legend>
<p>
Title:
<h:inputText value="#{blogManager.blog.title}" />
</p>
<p>
Text:
<h:inputTextarea value="#{blogManager.blog.text}" />
</p>
<p>
<h:commandButton action="#{blogManager.addBlog}" />
</p>
</fieldset>
</h:form>
You cannot inject a lower scoped object to a higher scoped object. Thus you cannot inject request scoped object blog to your session scoped blogManager. If you create it as a simple pojo, you can access it always like <h:inputText value="#{blogManager.blog.title}" />