Few month ago I've started my adventure with Hibernate 5. I'm creating a Spring Boot application which need to take data from database view.
I have created an JPA entity and write code responsible for running query and map results to ORM entity. The problem is that Hibernate generate proper query in traces, but returns duplicate of the first row as a list. Query created by Hibernate looks like:
select
revenuesum0_.revenueId as revenueI1_9_,
revenuesum0_.amount as amount2_9_,
revenuesum0_.calculatedDate as calculat3_9_,
revenuesum0_.revenueCalculatedDateId as revenueC4_9_,
revenuesum0_.categoryName as category5_9_,
revenuesum0_.timeSpan as timeSpan6_9_,
revenuesum0_.title as title7_9_,
revenuesum0_.userId as userId8_9_
from
RevenueSummaryView revenuesum0_
where
revenuesum0_.userId=?;
When I run it on DBeaver and in MySQL console it works, the result set is proper. Problem appear when I'm trying to take data using Java JPA Query execution. Code responsible for bug is here:
package org.pbt.dao;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.pbt.HibernateUtil;
import org.pbt.model.entity.ExpenseSummaryView;
import org.pbt.model.filter.Filter;
import org.springframework.stereotype.Repository;
import javax.persistence.Query;
import java.util.List;
#Repository
public class ExpenseSummaryViewDAOImpl implements ExpenseSummaryViewDAO {
private SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
#Override
public List<ExpenseSummaryView> getFiltered(Filter filter) {
Session session = this.sessionFactory.openSession();
String hql = "FROM ExpenseSummaryView WHERE userId = :userId ";
if (filter.getStartDate() != null && filter.getEndDate() != null) {
hql += "AND calculatedDate BETWEEN :startDate AND :endDate ";
} else if (filter.getStartDate() != null) {
hql += "AND calculatedDate >= :startDate ";
} else if (filter.getEndDate() != null) {
hql += "AND calculatedDate <= :endDate ";
}
Query query = session.createQuery(hql);
query.setParameter("userId", filter.getUserId());
if (filter.getStartDate() != null && filter.getEndDate() != null) {
query.setParameter("startDate", filter.getStartDate());
query.setParameter("endDate", filter.getEndDate());
} else if (filter.getStartDate() != null) {
query.setParameter("startDate", filter.getStartDate());
} else if (filter.getEndDate() != null) {
query.setParameter("endDate", filter.getEndDate());
}
List<ExpenseSummaryView> expenseSummaryViews = (List<ExpenseSummaryView>) query.getResultList();
session.close();
return expenseSummaryViews;
}
}
In a debug whole expenseSummaryViews list elements have the same object reference. The JPA entity looks like:
package org.pbt.model.entity;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.LocalDate;
#Entity
#Table(name = "ExpenseSummaryView")
public class ExpenseSummaryView {
#Id
#Column(name = "expenseId")
private int id;
#Column(name = "expenseCalculatedDateId", nullable = false)
private int calculatedDateId;
#Column(name = "userId", nullable = false)
private int userId;
#Column(name = "title", nullable = false)
private String title;
#Column(name = "amount", nullable = false)
private double amount;
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
#Column(name = "calculatedDate", nullable = false)
private LocalDate calculatedDate;
#Column(name = "categoryName", nullable = false)
private String categoryName;
#Column(name = "timeSpan", nullable = false)
private String timeSpan;
public int getId() {
return id;
}
public int getCalculatedDateId() {
return calculatedDateId;
}
public int getUserId() {
return userId;
}
public String getTitle() {
return title;
}
public double getAmount() {
return amount;
}
public LocalDate getCalculatedDate() {
return calculatedDate;
}
public String getCategoryName() {
return categoryName;
}
public String getTimeSpan() {
return timeSpan;
}
#Override
public String toString() {
return "{id:" + id + ", calculatedDateId:" + calculatedDateId + ", userId:" + userId + ", title:" + title + ", amount:" + amount + ", calculatedDate:" + calculatedDate.toString() + ", categoryName:" + categoryName + "}";
}
}
It is worth to mention that in very similar case I used hibernate for taking rows from another view on the same db in the same application and it works normally, but in above case it doesn't.
Is there anyone who can help?
problem was caused because there were no real ID available on database, the expenseId field was not real ID in DB, after change it, source code start works normally.
Related
Repostory
#Repository
public interface ClientRepository extends JpaRepository<ClientEntity, Long> {
#Modifying
#Transactional
#Query(value = "SELECT pp.id, TO_CHAR(pp.created_dt::date, 'dd.mm.yyyy')\n" +
"AS 'Data', CAST(pp.created_dt AS time(0)) AS 'Time', au.username AS 'UserName',\n" +
"ss.name AS 'Service', pp.amount AS 'Amount',\n" +
"REPLACE(pp.status, 'SUCCESS', 'Success') AS 'Payment_status', pp.account AS 'Account',\n" +
"pp.external_id AS 'Idn', COALESCE(pp.external_status, null, 'DN')\n" +
"AS 'Stat'\n" +
"FROM payments AS pp\n" +
"INNER JOIN user AS au ON au.id = pp.creator_id\n" +
"INNER JOIN services AS ss ON ss.id = pp.service_id\n" +
"WHERE pp.created_dt >= '2021-09-28'\n" +
"AND ss.name = 'Faberlic' AND pp.status = 'SUCCESS'", nativeQuery = true)
List<Client> getAllByRegDate();
}
Inteface
public interface Client {
Long getId();
#JsonFormat(shape = JsonFormat.Shape.STRING)
LocalDate getCreated_dt();
String getUsername();
String getName();
int getAmount();
String getStatus();
String getAccount();
String getExternal_id();
String getExternal_status();
}
DTO
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
public class ClientDto {
private Long id;
#JsonFormat(shape = JsonFormat.Shape.STRING)
private LocalDate created_dt;
private String username;
private String name;
private int amount;
private String status;
private String account;
private String external_id;
private String external_status;
public ClientDto(Client client) {
this.id = client.getId();
/...
/...
this.external_status = client.getExternal_status();
}
public ClientDto(ClientDto clientDto) {
this.id = clientDto.getId();
/...
this.external_status = clientDto.getExternal_status();
}
public ClientDto(ClientEntity clientEntity) {
}
#Override
public String toString() {
return "" + id + "|" + created_dt + "|" + username + "|" + name +
"|" + amount + "|" + status + "|" + account + "|" + external_id + "|" + external_status;
}
}
Entity
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Immutable
#Entity
#Table(name = "payments", schema = "public")
public class ClientEntity {
#Id
private Long id;
#Column(name = "created_dt")
private LocalDate created_dt;
#Column(name = "username")
private String username;
#Column(name = "name")
private String name;
#Column(name = "amount")
private int amount;
#Column(name = "status")
private String status;
#Column(name = "account")
private String account;
#Column(name = "external_id")
private String external_id;
#Column(name = "external_status")
private String external_status;
}
I am trying to save data to a csv file. I take data from one database, from three tables. In entity #Table in "name" I specify one of the existing tables - "payment". All data is taken from three tables (as I have written in Query). But when program is run, an error appears that the "name" column does not exist. This column is in another table from which I am fetching data. Can't figure out what I should do.
This is more of an answer to this question and the question you asked here, combined. Imho you are making things overly complex with your structure of having a Client interface which is used as a projection, which is then turned into a ClientDto (why? the projection is already a DTO) and you have your entities.
Instead of doing this just use a JdbcTemplate with a RowCallbackHandler to write the rows to CSV. This will use a lot less memory, be faster (as you aren't creating multiple objects per row to then throw it away, and you don't have all the rows in memory).
import java.io.FileWriter;
import java.sql.ResultSet;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
#Component
public class SchedulerService {
private static final String QUERY = "SELECT pp.id, pp.created_dt au.username, ss.name, pp.amount\n" +
"REPLACE(pp.status, 'SUCCESS', 'Success'), pp.account,\n" +
"pp.external_id AS 'Idn', COALESCE(pp.external_status, null, 'DN') AS 'Stat'\n" +
"FROM payments AS pp\n" +
"INNER JOIN user AS au ON au.id = pp.creator_id\n" +
"INNER JOIN services AS ss ON ss.id = pp.service_id\n" +
"WHERE pp.created_dt >= '2021-09-28'\n" +
"AND ss.name = 'Faberlic' AND pp.status = 'SUCCESS'";
private static final DateTimeFormatter date_format = DateTimeFormatter.ofPattern("dd.MM.yyyy");
private static final DateTimeFormatter time_format = DateTimeFormatter.ofPattern("HH:mm:ss");
private final JdbcTemplate jdbc;
public SchedulerService(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
#Scheduled(fixedRate = 5000)
public void downloadBlockedClients() {
String filename = "select.csv";
try (FileWriter writer = new FileWriter(filename)) {
writer.append("id|date|time|username|name|amount|status|account|external_id|external_status").append('\n');
this.jdbc.query(QUERY, (ResultSet rs) -> writeLine(writer, rs));
} catch (Exception e) {
e.printStackTrace();
}
}
private void writeLine(FileWriter writer, ResultSet rs) {
try {
LocalDateTime ldt = rs.getTimestamp("created_dt").toLocalDateTime();
writer.append(String.valueOf(rs.getLong("id")));
writer.append('|');
writer.append(ldt.format(date_format));
writer.append('|');
writer.append(ldt.format(time_format));
writer.append('|');
writer.append(rs.getString("username"));
writer.append('|');
writer.append(rs.getString("name"));
writer.append('|');
writer.append(String.valueOf(rs.getBigDecimal("amount")));
writer.append('|');
writer.append(rs.getString("status"));
writer.append('|');
writer.append(rs.getString("account"));
writer.append('|');
writer.append(rs.getString("idn"));
writer.append('|');
writer.append(rs.getString("stat"));
writer.append('\n');
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
Something along these lines will make your resources more efficient (saves the copying, having results duplicated in memory) and should be faster. You could move the row handling to a method so your lambda gets a bit more readable.
NOTE: I assumed that you are using Spring Boot and that the `JdbcTemplate is available out-of-the-box. If not you need to configure one next to your JPA configuration.
#Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
I have a Java RESTapi, where I want to convert a list of my custom Pet object into Json, and display it in an endpoint.
This I what I have so far:
#Path("/allPets")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getPetsfromCollection() {
List<Pet> petList = new ArrayList<>();
petList.addAll(facade.returnAllPets());
String json = gson.toJson(petList);
//TODO return proper representation object
return Response.ok().entity(json).build();
}
I'm using the facade pattern where I have a method of adding Java entities to a list as such:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("PetHospitaljpa");
public Collection<Pet> returnAllPets (){
EntityManager em = emf.createEntityManager();
//vi laver en typed query for at specificere hvilken datatype,
// det er vi leder efter, i dette tilfælde er det en Pet
TypedQuery<Pet> query = em.createNamedQuery("Pet.findAll", Pet.class);
return query.getResultList();
}
I'm returning a collection in case I want to change the data structure of ArrayList to something else later.
I have tried several workarounds, but I keep getting a stack overflow error.
Iøm aware of the fact, that I need to use DTO's instead, and I have made a custom method to change entities to DTO's as such:
public static DTOPet converttoDTO(Pet entity){
DTOPet dto = new DTOPet();
dto.setId(entity.getId());
dto.setName(entity.getName());
dto.setBirth(entity.getBirth());
dto.setDeath(entity.getDeath());
dto.setSpecies(entity.getSpecies());
return dto;
}
I'm not sure if this is good code practice if I there is something else I can do instead to transform a collection of entities into DTO's?
As pointed out. The problem occurs because I have a circular reference.
inside my Pet Entity class:
#ManyToOne
private Owner ownerId;
inside my Owner Entity class:
#OneToMany(mappedBy = "ownerId")
private Collection<Pet> petCollection;
My Pet Class:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package Entities;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
/**
*
* #author kristoffer
*/
#Entity
#Table(name = "pet")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Pet.findAll", query = "SELECT p FROM Pet p")
, #NamedQuery(name = "Pet.findById", query = "SELECT p FROM Pet p WHERE p.id = :id")
, #NamedQuery(name = "Pet.findByName", query = "SELECT p FROM Pet p WHERE p.name = :name")
, #NamedQuery(name = "Pet.findByBirth", query = "SELECT p FROM Pet p WHERE p.birth = :birth")
, #NamedQuery(name = "Pet.findBySpecies", query = "SELECT p FROM Pet p WHERE p.species = :species")
, #NamedQuery(name = "Pet.findByDeath", query = "SELECT p FROM Pet p WHERE p.death = :death")})
public class Pet implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 45)
#Column(name = "name")
private String name;
#Basic(optional = false)
#NotNull
#Column(name = "birth")
#Temporal(TemporalType.DATE)
private Date birth;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 45)
#Column(name = "species")
private String species;
#Column(name = "death")
#Temporal(TemporalType.DATE)
private Date death;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "petId")
private Collection<Event> eventCollection;
#JoinColumn(name = "owner_id", referencedColumnName = "id")
#ManyToOne
private Owner ownerId;
public Pet() {
}
public Pet(Integer id) {
this.id = id;
}
public Pet(Integer id, String name, Date birth, String species) {
this.id = id;
this.name = name;
this.birth = birth;
this.species = species;
}
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 Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
public Date getDeath() {
return death;
}
public void setDeath(Date death) {
this.death = death;
}
#XmlTransient
public Collection<Event> getEventCollection() {
return eventCollection;
}
public void setEventCollection(Collection<Event> eventCollection) {
this.eventCollection = eventCollection;
}
public Owner getOwnerId() {
return ownerId;
}
public void setOwnerId(Owner ownerId) {
this.ownerId = ownerId;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Pet)) {
return false;
}
Pet other = (Pet) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "Pet{" + "id=" + id + ", name=" + name + ", birth=" + birth + ", species=" + species + ", death=" + death + ", eventCollection=" + eventCollection + ", ownerId=" + ownerId + '}';
}
}
EDIT:
I tried creating a method, where I convert all the objects to DTO's, but the string is still empty when it is displayed:
#Path("/allPets")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getPetsfromCollection() {
//med denne metode skal vi bruge et DTO(data transfer object til at formatere til Json)
List<Pet> petList = new ArrayList<>();
List<DTOPet> DTOPetList = new ArrayList<>();
petList.addAll(facade.returnAllPets());
for(Pet pet: petList){
DTOPet dtopet = EntitytoDTO.converttoDTO(pet);
DTOPetList.add(dtopet);
}
String json = gson2.toJson(DTOPetList);
return Response.ok().entity(json).build();
}
When I use the debugger, the new list is created successfully, with the right parameters, but the String JSON is just created like this [{},{},{},{}], even though I use GSON
You need to detect what place of error. I recommend to add debug information, like
#Path("/allPets")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getPetsfromCollection() {
log.debug("getPetsfromCollection start");
List<Pet> petList = new ArrayList<>(facade.returnAllPets());
log.debug("petList" + petList.length());
String json = gson.toJson(petList);
log.debug("json " + json);
//TODO return proper representation object
return Response.ok().entity(json).build();
}
EntityManagerFactory emf = Persistence.createEntityManagerFactory("PetHospitaljpa");
public Collection<Pet> returnAllPets (){
log.debug("returnAllPets start");
EntityManager em = emf.createEntityManager();
log.debug("createNamedQuery start");
TypedQuery<Pet> query = em.createNamedQuery("Pet.findAll", Pet.class);
log.debug("single result" + query.getSingleResult() );
TypedQuery<Pet> query = em.createNamedQuery("Pet.findAll", Pet.class);
log.debug("list result" + query.getResultList());
TypedQuery<Pet> query = em.createNamedQuery("Pet.findAll", Pet.class);
return query.getResultList();
}
P.S. Also, please show Pet class, may be problem is with this class.
Update: I recommend also to try temporary delete:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "petId")
private Collection<Event> eventCollection;
And / or
#JoinColumn(name = "owner_id", referencedColumnName = "id")
#ManyToOne
private Owner ownerId;
And check do you have such SO exception or not. It is look like Event or Owner table is too big or have circle dependencies.
Without seeing what the "Pet" class looks like, it is difficult to pinpoint the problem. I suspect you have a variable of another class in your Pet class that also has a reference to the pet class itself (creating a circular reference that would cause a stack overflow in the serialization process)
I have a problem while I'm trying to pull data from my DB requesting to be in a specific object type. I've created query, which is fetching objects of Java Object type not the type that I need. Here is my DAO class:
import com.jackowiak.Domain.TurbinesData;
import com.jackowiak.Model.TurbineDataCSVReader;
import com.jackowiak.Utils.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.query.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import java.util.List;
public class TurbinesDaoBean {
private static final Logger LOG = LoggerFactory.getLogger(TurbinesDaoBean.class);
public List<TurbinesData> getTurbineDataFromDB(String turbineName) {
LOG.info("Initializating DB connection to get turbine data");
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
Query query = session.createQuery("select windSpeed, turbinePower from TurbinesData where turbineName = :turbineName");
query.setParameter("turbineName", turbineName);
session.getTransaction().commit();
List<TurbinesData> results = query.list();
LOG.debug("Data for turbine " + turbineName + " collected successfully");
return results;
}
}
And here is my Entity class:
#Entity
#Table(name = "TurbinesData")
public class TurbinesData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
protected long id;
#Column(nullable = false, length = 50, name = "Nazwa_turbiny")
protected String turbineName;
#Column(nullable = false, length = 20, name = "V_wiatru")
protected Double windSpeed;
#Column(nullable = false, length = 20, name = "Moc_turbiny")
protected Double turbinePower;
public TurbinesData() {
}
public TurbinesData(Double windSpeed, Double turbinePower) {
this.windSpeed = windSpeed;
this.turbinePower = turbinePower;
}
public TurbinesData(String turbineName, Double windSpeed, Double turbinePower) {
this.turbineName = turbineName;
this.windSpeed = windSpeed;
this.turbinePower = turbinePower;
}
// getters and setters
}
I would like to receive list of TurbinesData objects after executing query
Change the jpql to:
"FROM TurbinesData td WHERE td.turbineName = :turbineName"
And then use TypedQuery
EDIT:
According to your comment you want to retrieve only two fields. You need to do:
"SELECT NEW package.to.TurbinesData(td.windSpeed, td.turbinePower) FROM TurbinesData td WHERE td.turbineName = :turbineName"
Note:
Need to have proper constructor defined.
Need to use fully qualified name
You can typecast List<Object> to List<custom class>
Like.
return (List<T>) query.list();
Hope that helps.
This question What is the right way to use entitymanager
supplied a link to EntityManagerHelper.java which I added to my code base. When I use this helper class to make subsequent calls to the database it returns previous results to the same query.
The scenario I see this most in is retrieving the lastentry property of my User Class. On the browser I make an AJAX request and the servlet partial below gets the user object and calls a method to return my lastentry.
I've read about .clear() but I get server errors when I added it to my EntityManagerHelper. I would like to avoid creating an EntityManager every time I want to make a call to the db.
How can I fix this problem?
partial from servlet
User user = User.getUser();
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(user.getLastentry());
User Class
package entities;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Persistence;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.shiro.SecurityUtils;
import responseablees.EntityManagerHelper;
/**
*
* #author Christopher Loughnane <chrisloughnane1#gmail.com>
*/
#Entity
#Table(name = "user")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "User.findAll", query = "SELECT u FROM User u")
, #NamedQuery(name = "User.findById", query = "SELECT u FROM User u WHERE u.id = :id")
, #NamedQuery(name = "User.findByUsername", query = "SELECT u FROM User u WHERE u.username = :username")
, #NamedQuery(name = "User.getUsernameByUserEmail", query = "SELECT u.username FROM User u WHERE u.useremail = :useremail")
, #NamedQuery(name = "User.findByUserEmail", query = "SELECT u FROM User u WHERE u.useremail = :useremail")
, #NamedQuery(name = "User.findByPassword", query = "SELECT u FROM User u WHERE u.password = :password")})
public class User implements Serializable {
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 2048)
#Column(name = "lastentry")
private String lastentry;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 128)
#Column(name = "useremail")
private String useremail;
#Basic(optional = false)
#Column(name="created", insertable = false, updatable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date created;
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(name = "username")
private String username;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(name = "password")
private String password;
public User() {
}
public User(Integer id) {
this.id = id;
}
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof User)) {
return false;
}
User other = (User) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "DAOs.User[ id=" + id + " ]";
}
public String getUseremail() {
return useremail;
}
public void setUseremail(String useremail) {
this.useremail = useremail;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public String getLastentry() {
return lastentry;
}
public void setLastentry(String lastentry) {
this.lastentry = lastentry;
}
public static User getUser(){
String currentUser = (String) SecurityUtils.getSubject().getPrincipal();
User user = (User) em.createNamedQuery("User.findByUserEmail")
.setParameter("useremail", currentUser)
.getSingleResult();
return user;
}
}
Requested Code
Verbose Approach
String currentUser = (String) SecurityUtils.getSubject().getPrincipal();
EntityManagerFactory emfactory = Persistence.createEntityManagerFactory("com.mycompany_responseableES_war_1.0-SNAPSHOTPU");
EntityManager em = emfactory.createEntityManager();
User user = (User) em.createNamedQuery("User.findByUserEmail")
.setParameter("useremail", currentUser)
.getSingleResult();
em.getTransaction().begin();
user.setLastentry(JSON);
em.getTransaction().commit();
EntityManagerHelper Approach
User user = User.getUser();
EntityManager em = EntityManagerHelper.getEntityManager();
em.getTransaction().begin();
user.setLastentry(JSON);
em.getTransaction().commit();
First of all, I would be careful to put ALL my data fetching/modification code between
em.getTransaction().begin()
and
em.getTransaction().commit()
This ensures that your entities are managed and your changes are automatically persisted. In your code it looks like the User instance comes from outside the transaction. I have not worked much with JPA and manual transactions, but I would not be surprised if the user is detached (that is, not managed by the EntityManager) and so changes are not automatically persisted to the database. If that were the case I guess you could solve it by putting
// merge(...) takes an entity that is not managed, apply changes to
// the persistence context and returns a new *managed* entity.
// The original entity REMAINS *detached* or *non persistent*
User managedUser = em.merge(user);
// I assume that you have something like this to set your user in the
// current session or whatever
User.setUser(managedUser);
just before the commit.
JPA vocabulary can be a bit hard at first, read this for more details about the JPA entity lifecycle (specially the diagram at the bottom).
I've got a #ViewScoped bean that calls a #Stateless bean which does a simple query to return some values from my DB.
This should be enough to make the query everytime I load the page, and this should lead me to have always updated data on each page load.
But this won't work, and I don't know how to solve it!
My query returns the old value, even after changing it with MySql Workbench.
(Doing the query on Workbench returns correct data!)
Here's the code :
DispensaListBean.java
package ManagedBeans;
import ejb.DispensaManager;
import ejb.DispensaManagerLocal;
import entity.Dispensa;
import java.util.List;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
/**
*
* #author stefano
*/
#ManagedBean
#ViewScoped
public class DispensaListBean {
#EJB
private DispensaManagerLocal dispensaManager;
/**
* Creates a new instance of DIspensaListBean
*/
public DispensaListBean() {
}
public List<Dispensa> getTopDispense(){
List<Dispensa> l = dispensaManager.findByVoto(DispensaManager.DESC);
for(Dispensa d : l){
System.out.println(d.getTitolo() + " | " + d.getVoto()); //This code prints ALWAY the old getVoto() value, it takes the new one just after restarting the server
}
return l;
}
public List<Dispensa> getDispense(){
return dispensaManager.findAll();
}
public Dispensa getById(int i){
return dispensaManager.findById(i);
}
}
DispensaManager.java
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package ejb;
import entity.Dispensa;
import facade.DispensaFacadeLocal;
import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Stateless;
/**
*
* #author stefano
*/
#Stateless
public class DispensaManager implements DispensaManagerLocal {
public static final int ASC=0, DESC=1;
#EJB
private DispensaFacadeLocal dispensaFacade;
#Override
public java.util.List<Dispensa> findByVoto(int order) {
return (order==DispensaManager.ASC) ? dispensaFacade.findByVotoAsc() : dispensaFacade.findByVotoDesc();
}
#Override
public List findAll() {
return dispensaFacade.findAll();
}
#Override
public Dispensa findById(int id) {
return dispensaFacade.find(id);
}
}
DispensaFacade.java
package facade;
import entity.Dispensa;
import entity.Post;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
/**
*
* #author stefano
*/
#Stateless
public class DispensaFacade extends AbstractFacade<Dispensa> implements DispensaFacadeLocal {
#PersistenceContext(unitName = "UNILIFE-ejbPU")
private EntityManager em;
#Override
protected EntityManager getEntityManager() {
return em;
}
public DispensaFacade() {
super(Dispensa.class);
}
#Override
public List<Dispensa> findByVotoDesc() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Dispensa> q = cb.createQuery(Dispensa.class);
Root<Dispensa> c = q.from(Dispensa.class);
q.select(c);
q.where(cb.isNotNull(c.get("datiFile")));
q.orderBy(cb.desc(c.get("voto")));
TypedQuery<Dispensa> typedQuery = em.createQuery(q);
return typedQuery.getResultList();
}
#Override
public java.util.List<Dispensa> findByVotoAsc() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Dispensa> q = cb.createQuery(Dispensa.class);
Root<Dispensa> c = q.from(Dispensa.class);
q.select(c);
q.where(cb.isNotNull(c.get("datiFile")));
q.orderBy(cb.asc(c.get("voto")));
TypedQuery<Dispensa> typedQuery = em.createQuery(q);
return typedQuery.getResultList();
}
}
Dispensa.java
package entity;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
/**
*
* #author stefano
*/
#Entity
#Table(name = "Dispensa")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Dispensa.findAll", query = "SELECT d FROM Dispensa d"),
#NamedQuery(name = "Dispensa.findById", query = "SELECT d FROM Dispensa d WHERE d.id = :id"),
#NamedQuery(name = "Dispensa.findByTitolo", query = "SELECT d FROM Dispensa d WHERE d.titolo = :titolo"),
#NamedQuery(name = "Dispensa.findByDescrizione", query = "SELECT d FROM Dispensa d WHERE d.descrizione = :descrizione"),
#NamedQuery(name = "Dispensa.findByTag", query = "SELECT d FROM Dispensa d WHERE d.tag = :tag"),
#NamedQuery(name = "Dispensa.findByData", query = "SELECT d FROM Dispensa d WHERE d.data = :data"),
#NamedQuery(name = "Dispensa.findByVoto", query = "SELECT d FROM Dispensa d WHERE d.voto = :voto"),
#NamedQuery(name = "Dispensa.findByNumVoti", query = "SELECT d FROM Dispensa d WHERE d.numVoti = :numVoti"),
#NamedQuery(name = "Dispensa.findByNumDownloads", query = "SELECT d FROM Dispensa d WHERE d.numDownloads = :numDownloads")})
public class Dispensa implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 50)
#Column(name = "titolo")
private String titolo;
#Size(max = 255)
#Column(name = "descrizione")
private String descrizione;
#Size(max = 255)
#Column(name = "tag")
private String tag;
#Basic(optional = true)
#NotNull
#Lob
#Column(name = "datiFile")
private byte[] datiFile;
#Basic(optional = false)
#NotNull
#Column(name = "data")
#Temporal(TemporalType.DATE)
private Date data;
#Basic(optional = false)
#NotNull
#Column(name = "voto")
private int voto;
#Basic(optional = false)
#NotNull
#Column(name = "numVoti")
private int numVoti;
#Basic(optional = false)
#NotNull
#Column(name = "numDownloads")
private int numDownloads;
#JoinTable(name = "Scaricati", joinColumns = {
#JoinColumn(name = "dispensa", referencedColumnName = "id")}, inverseJoinColumns = {
#JoinColumn(name = "utente", referencedColumnName = "username")})
#ManyToMany(fetch = FetchType.LAZY)
private Collection<Utente> downloaders;
#JoinColumn(name = "materia", referencedColumnName = "id")
#ManyToOne(optional = true)
private Materia materia;
#JoinColumn(name = "autore", referencedColumnName = "username")
#ManyToOne(optional = false)
private Utente autore;
public Dispensa() {
}
public Dispensa(Integer id) {
this.id = id;
}
public Dispensa(Integer id, String titolo, byte[] datiFile, Date data, int voto, int numVoti, int numDownloads) {
this.id = id;
this.titolo = titolo;
this.datiFile = datiFile;
this.data = data;
this.voto = voto;
this.numVoti = numVoti;
this.numDownloads = numDownloads;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitolo() {
return titolo;
}
public void setTitolo(String titolo) {
this.titolo = titolo;
}
public String getDescrizione() {
return descrizione;
}
public void setDescrizione(String descrizione) {
this.descrizione = descrizione;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
public byte[] getDatiFile() {
return datiFile;
}
public void setDatiFile(byte[] datiFile) {
this.datiFile = datiFile;
}
public Date getData() {
return data;
}
public void setData(Date data) {
this.data = data;
}
public int getVoto() {
return voto;
}
public void setVoto(int voto) {
this.voto = voto;
}
public int getNumVoti() {
return numVoti;
}
public void setNumVoti(int numVoti) {
this.numVoti = numVoti;
}
public int getNumDownloads() {
return numDownloads;
}
public void setNumDownloads(int numDownloads) {
this.numDownloads = numDownloads;
}
#XmlTransient
public Collection<Utente> getDownloaders() {
return downloaders;
}
public void setDownloaders(Collection<Utente> utenteCollection) {
this.downloaders = utenteCollection;
}
public Materia getMateria() {
return materia;
}
public void setMateria(Materia materia) {
this.materia = materia;
}
public Utente getAutore() {
return autore;
}
public void setAutore(Utente autore) {
this.autore = autore;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Dispensa)) {
return false;
}
Dispensa other = (Dispensa) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "entity.Dispensa[ id=" + id + " ]";
}
}
Now, I've faced this problem before with other entities and methods, and I solved it by refreshing the entities, but why should I refresh an entity in this case if I get it from the database everytime that I load the page?
It's just nonsense!
From the code itself it doesn't look like you're doing any explicit caching yourself. #ViewScoped, #RequestScoped and isPostback are all not relevant here, and on the contrary, the purpose of those scopes is actually to do caching, instead of letting the backing bean call through to the service each and every time.
That however is almost the opposite of your problem.
In case you get stale entities from the entity manager, it's almost always a case of an L2 cache. Did you configure any in persistence.xml? Which JPA implementation do you use?
Also important, where and how do you update your data? The code as given doesn't show it. You do mention this "even after changing it with MySql Workbench"
In the case that a JPA Level 2 (L2) cache is used, JPA will get the entities from this cache. Without counter measures, it will track changes to those entities only if they are modified via JPA. If you update the underlying data yourself, either directly via JDBC or via some other external system (like MySql Workbench), JPA will not be aware of those changes.
My instinct is that you have a stale cache of some sort.
Have you read this article?
I would first focus on your Session Bean. Create a test harness without the extra complexity of JSF pages.
I was expecting the default transaction behaviour of your Stateless bean to be "sensible", but I'm now wondering whether using
#TransactionAttribute(TransactionAttributeType.REQUIRED)
might solve your problem.
Most probably this is caused by MySQL's default isolation level which is REPEATABLE READ.
This means that you don't see changes done by other transactions until you end (commit, rollback) your "own" transaction (remember: a SELECT already starts a transaction)
I assume the EJB connection is taken from a connection pool and thus the transactions that are started are never ended properly. Try issuing a commit or rollback before running the select from within your web application.
For a permanent solution you can either change the default isolation by configuring your connection pool (most of them allow this), change the transaction level by calling setTransactionIsolation() on the connection or by changing the default isolation level in MySQL.
Are you using hibernate as your EntityManager? If so, it might be using the Session cache, and storing your object. In which case, if you change the data either through SQL or through a different session, you might need to call "refresh" on your object in order to pick up the changes.
Have you tried to change your bean to #RequestScoped ?
You need know if your page is a postback, http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/javax/faces/render/ResponseStateManager.html
Something like that
ResponseStateManager rsm = FacesContext.getCurrentInstance().getRenderKit().getResponseStateManager();
if (!rsm.isPostback(FacesContext.getCurrentInstance())) {
//do some stuff
}