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}" />
Related
First of all I have two tables job, category which are in diagram as
and my entities are :
#Entity
#Table( name = TableName.JOB_TABLE)
public class Job {
#Id
#GeneratedValue
private Integer id;
private String title;
private String description;
#OneToMany(mappedBy = "job")
private List<Category> categories;
// omitting setters an getters for brevity
}
and
#Entity
#Table( name = TableName.CATEGORY_TABLE)
public class Category {
#Id
#GeneratedValue
private Integer id;
private String name;
#ManyToOne
#JoinColumn(name = "job_id")
private Job job;
// omitting setters an getters for brevity
}
JobService is
#Service
public class JobService implements IDatabaseCrud<Job>{
#Autowired
private JobRepository jobRepository;
#Autowired
private CategoryRepository categoryRepository;
public void saveCategory(Job job) {
List<Category> categories = job.getCategories();
for (Category category : categories) {
category.setJob(job);
categoryRepository.save(category);
}
}
#Override
public void save(Job obj) {
// TODO Auto-generated method stub
jobRepository.save(obj);
saveCategory(obj);
}
}
now I don't have any idea to save new job where I've to save one Job with many categories selected from list.
<form:form commandName="job">
<form:input path="title"/><br>
<form:input path="company"/><br>
<form:input path="location"/><br>
<form:input path="url"/><br>
<form:input path="email"/><br>
<form:input path="description"/><br>
<form:select path="categories">
<form:options items="${categories}" itemValue="id" itemLabel="name"/>
</form:select><br>
<form:input path="createdAt"/><br>
<form:input path="toApply"/><br>
<input type="submit" value="Add Job">
</form:form>
the above form is not submitting data to controller and gives error HTTP Status 400 -The request sent by the client was syntactically incorrect. following controller I want to save these details to DB
#Controller
public class JobController {
private static final Logger logger = LoggerFactory.getLogger(JobController.class);
#Autowired
private JobService jobService;
#Autowired
private CategoryService categoryService;
#ModelAttribute("job")
public Job constructJob() {
return new Job();
}
#RequestMapping(value = "/jobs", method = RequestMethod.GET)
public String showJobs(Model model) {
model.addAttribute("jobs", jobService.findAll());
return "jobs";
}
#RequestMapping(value = "/jobs/{id}", method = RequestMethod.GET)
public String showJobDetail(Model model, #PathVariable Integer id) {
model.addAttribute("job", jobService.findJobWithCategories(id));
return "job-detail";
}
#RequestMapping(value = "/show-add-job", method = RequestMethod.GET)
public String showJobForm(Model model) {
model.addAttribute("categories", categoryService.findAll());
return "add-job";
}
#RequestMapping(value = "/show-add-job", method = RequestMethod.POST)
public String addJobDetail(#ModelAttribute("job") Job job) {
///jobService.save(job);
List<Category> categories = job.getCategories();
for (Category category : categories) {
logger.info("DEBUG job object", category);
}
return "redirect:/jobs";
}
}
with the above stuff I'm unable to save job with categories when I submit the form I get HTTP Status 400. is some thing wrong in form.
This is URL to that project.
The problem you are getting is related to how you bind the categories, in fact you need to help the framework resolve them, e.g. with a help of WebDataBinder. You should add something like
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Category.class,
new PropertyEditorSupport() {
#Override
public void setAsText(String text) {
// replace the dao with your appropriate repository call
Category category = dao.find(Category.class,
Integer.parseInt(text));
setValue(category);
}
});
}
about the createdAt
the trouble you're facing there is that you need to tell the framework in which format are you entering the date. For example, if you are passing the date in the format of yyyy\MM\dd, than it will simply work.
If you are using some other format it will suffice to annotate the property with #DateTimeFormat. A concrete example, you can annotate your property inside the Job class with
#DateTimeFormat(iso= DateTimeFormat.ISO.DATE)
#Column(name = "created_at")
private Date createdAt;
DateTimeFormat.ISO.DATE expects a date in yyyy-MM-dd format, or use a pattern attribute, and the format you prefer
Hello i checked out your code and found that you have three issues: one is related to submission of the date field - createdAt, the other one is with the applyTo field and the other one is with categories field.
About the problem with dates you should check documentation for spring #InitBinder. Here is a good SO thread for it. Basic problem is that spring don't know how to bind date to String.
Other problem with categories field is that you should implement a Converter that will convert categoryId which is a number to Category class. see here good thread.
if you remove all three fields - applyTo,createdAt and categories, your form submission will work. but if you want to work as expected please implement #initBinder for date and Converter for categories
I have a web application that allows me to add a Book object to a list displayed as a "datatable" using JSF, EJB, MVC, JPA.
The View code "listBooks.xhtml":
<h:dataTable value="#{bookController.booklist}" var="elementBook" border="1" cellpadding="5">
<h:column>
<f:facet name="isbn">
<h:outputText value="ISBN"/> </f:facet>
<h:outputText value="#{elementBook.isbn}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Title"/></f:facet>
<h:outputText value="#{elementBook.title}"/>
</h:column>
/* rest of the code */
</h:dataTable>
The View Code "addNewBook.xhtml" :
<h:form>
<h4>
ISBN:
<h:inputText value="#{bookController.book.isbn}" size = "10" /> <br />
Title:
<h:inputText value="#{bookController.book.title}" size = "10" /> <br />
Price:
<h:inputText value="#{bookController.book.price}" size = "10" /> <br />
Description:
<h:inputTextarea value="#{bookController.book.description}" /> <br />
Number of pages:
<h:inputText value="#{bookController.book.bnOfPage}" size = "10" /> <br />
Illustrations:
<h:selectBooleanCheckbox value="#{bookController.book.illustrations}"/> <br />
<h:commandButton value="Create a book" action="#{bookController.doCreateBook()}" />
</h4>
<hr/>
<h2> Librairie en ligne</h2>
</h:form>
Controller Layer :
#ManagedBean
#RequestScoped
public class BookController {
#EJB
private BookEJB bookEJB;
private Book book;
private List<Book> booklist;
public BookController() {
book = new Book();
booklist=new ArrayList();
}
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
public List<Book> getBooklist() {
return booklist;
}
public void setBooklist(List<Book> booklist) {
this.booklist = booklist;
}
public String doCreateBook() {
bookEJB.create(book);
booklist= bookEJB.findAll();
return "listBooks.xhtml";
}
}
Business Logic Layer :
#Stateless
public class BookEJB extends AbstractFacade<Book> {
#PersistenceContext(unitName = "tpbookPU")
private EntityManager em;
#Override
protected EntityManager getEntityManager() {
return em;
}
public BookEJB() {
super(Book.class);
}
}
And an Abstract Facade :
public abstract class AbstractFacade<T> {
private Class<T> entityClass;
public AbstractFacade(Class<T> entityClass) {
this.entityClass = entityClass;
}
protected abstract EntityManager getEntityManager();
public void create(T entity) {
getEntityManager().persist(entity);
}
public void edit(T entity) {
getEntityManager().merge(entity);
}
public void remove(T entity) {
getEntityManager().remove(getEntityManager().merge(entity));
}
//......
And finally the Entity "Book" :
#Entity
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private Float price;
private String description;
private String isbn;
private Integer bnOfPage;
private Boolean illustrations;
public Book(){
}
public Integer getBnOfPage() {
return bnOfPage;
}
public String getDescription() {
return description;
}
public Boolean getIllustrations() {
return illustrations;
}
public String getIsbn() {
return isbn;
}
public Float getPrice() {
return price;
}
The controller class "BookController", its job is to use the view to update the model, it declares a book variable private Book book; and the same for the EJB, private BookEJB bookEJB; and then using this bookEJB.create(book);
Shouldn't it be like BookEJB bookEJB = new BookEJB(); and then doing the bookEJB.create(book); in the doCreateBook() method. Shouldn't we always use the new word to be able to use the object and access their methods ? because if we used just the name of the class instead we'll access just the static methods.
And then why using the new Keyword in the BookController constructor ? Book book = new Book(); ?
I have Offer entity class :
#Entity
public class Offer {
public Offer(){}
private int offerId;
private String offerBanner;
private String offerLongDesc;
private int offerLikeCount ;
List<Category> categoryList ;
#DateTimeFormat(pattern= "dd/MM/yyyy")
private Date offerStartDate;
<----all getters and setters---->
}
And a Category entity class
#Entity
public class Category{
public Category() {}
private int categoryId;
private int categoryParentId;
private String categoryName;
private Date categoryCreationDate;
<--getters n setters--->
}
In my offerDetails form i am trying to bind the categoryList (attribute Offer entity) to checkbox
offerEntry.jsp
<form:form class="form-horizontal" action="/addOffer" commandName="offerDetails" method="post" enctype="multipart/form-data" style=" margin-top:20px; padding-right:50px; padding-left:10px; ">
<div class="checkbox-list">
<c:forEach var="category" varStatus="categoryStatus" items="${categoriesList}">
<form:checkbox path="categoryList" value="${category.categoryId}"/> <c:out value="${category.categoryName}" /><br>
</c:forEach>
</div>
<-----Other div elements------>
</form:form>
I have Offer entity class :
#Entity
public class Offer {
public Offer(){}
private int offerId;
private String offerBanner;
private String offerLongDesc;
private int offerLikeCount ;
List<Category> categoryList ;
#DateTimeFormat(pattern= "dd/MM/yyyy")
private Date offerStartDate;
<----all getters and setters---->
}
And a Category entity class
#Entity
public class Category{
public Category() {}
private int categoryId;
private int categoryParentId;
private String categoryName;
private Date categoryCreationDate;
<--getters n setters--->
}
In my offerDetails form i am trying to bind the categoryList (attribute Offer entity) to checkbox
offerEntry.jsp
<form:form class="form-horizontal" action="/addOffer" commandName="offerDetails" method="post" enctype="multipart/form-data" style=" margin-top:20px; padding-right:50px; padding-left:10px; ">
<div class="checkbox-list">
<c:forEach var="category" varStatus="categoryStatus" items="${categoriesList}">
<form:checkbox path="categoryList" value="${category.categoryId}"/> <c:out value="${category.categoryName}" /><br>
</c:forEach>
</div>
<-----Other div elements------>
</form:form>
and here is my controller :
#RequestMapping(value = {"/addOffer"}, method = RequestMethod.GET)
public ModelAndView offerForm() {
ModelAndView mv = new ModelAndView("offerEntry");
Offer offer = new Offer();
try{
List<Category> categoryList=categoryRepository.getAllCategories();
mv.addObject("categoriesList",categoryList);
}catch(Exception e)
{
}
mv.addObject("offerDetails",offer);
return mv;
}
Converter class :
public class CategoryIdtoCategory implements Converter<String, Category>
{
#Inject
private CategoryRepository categoryRepo;
#Override
public Category convert(String categoryId)
{
try{
int categoryIdI = Integer.parseInt(categoryId);
return categoryRepo.findCategoryById(categoryIdI);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
On Submit i want selected checkbox values to populate offerDetails.categoryList collection.
I have registered converter as well (for converting those category Ids to categoryObjects)
Bean registration:
<beans:bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" >
<beans:property name="converters">
<beans:list>
<beans:bean class="com.service.util.CategoryIdtoCategory"/>
</beans:list>
</beans:property>
</beans:bean>
I am still getting following error :
[Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'categoryList'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [com.domain.Category] for property 'categoryList[0]': no matching editors or conversion strategy found]
I am new to Spring . Please Excuse me if its a silly question.
Your help would be appreciated . :)
In the JSP change path to reference the category
In category entity override equal and hash methods
In converter convert result of toString method in the Category entity to a Category object.
You can solve with #InitBinder in your controller like :
#InitBinder
protected void initBinder(WebDataBinder binder) throws Exception{
binder.registerCustomEditor(Set.class,"categoryList", new CustomCollectionEditor(Set.class){
protected Object convertElement(Object element){
if (element instanceof String) {
Category category = categoryCache.get(Integer.parseInt(element.toString()));
return role;
}
return null;
}
});
}
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;
}
});
}
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.