I have a really strange sometimes if I load my data with JPA some enetities have null values even if there are values in the database.
The data is not newly created and it's not happening to every record.
The only value which is set is the primary key
I coulnd't find anything similar while researching
AbstarctFacade.java
import java.util.List;
import javax.persistence.EntityManager;
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));
}
public void detach(T entity) {
getEntityManager().detach(entity);
}
public T find(Object id) {
return getEntityManager().find(entityClass, id);
}
public List<T> findAll() {
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
return getEntityManager().createQuery(cq).getResultList();
}
}
ItemFacade.java
import ch.itwork.map.core.util.AbstractFacade;
import javax.ejb.Stateless;
import javax.persistence.Cache;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
#Stateless
public class ItemFacade extends AbstractFacade<Item> {
#PersistenceContext(unitName = "...")
private EntityManager em;
#Override
protected EntityManager getEntityManager() {
em.clear();
Cache cache = em.getEntityManagerFactory().getCache();
cache.evict(Item.class);
return em;
}
public void clearCache() {
em.clear();
Cache cache = em.getEntityManagerFactory().getCache();
cache.evict(Item.class);
}
public ItemFacade() {
super(Item.class);
}
}
This is the call which causes the problem.
getItemFacade().findAll();
Related
I need to have generic service and repository classes in my project. I wrote it this way, but I don't know if it is correct or not.
BaseModel:
#MappedSuperclass
#Data
public class AbstractBaseEntity implements Serializable {
#Id
#GeneratedValue
private Long id;
#Version
private int version;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public AbstractBaseEntity() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
}
GenericService:
public interface AbstractBaseService<T extends AbstractBaseEntity, ID extends Serializable>{
public abstract T save(T entity);
public abstract List<T> findAll(); // you might want a generic Collection if u prefer
public abstract Optional<T> findById(ID entityId);
public abstract T update(T entity);
public abstract T updateById(T entity, ID entityId);
public abstract void delete(T entity);
public abstract void deleteById(ID entityId);
// other methods u might need to be generic
}
Here I inherited from base service class and Autowired repository base classes.
#Service
#Transactional
public abstract class AbstractBaseServiceImpl<T extends AbstractBaseEntity, ID extends Serializable>
implements AbstractBaseService<T, ID> {
#Autowired
private AbstractBaseRepository<T, ID> abstractBaseRepository;
protected abstract Class<T> getDomaimClass();
protected Class<T> domainClass = this.getDomaimClass();
#Override
public T save(T entity) {
return (T) abstractBaseRepository.save(entity);
}
#Override
public List<T> findAll() {
return abstractBaseRepository.findAll();
}
#Override
public Optional<T> findById(ID entityId) {
return abstractBaseRepository.findById(entityId);
}
#Override
public T update(T entity) {
return (T) abstractBaseRepository.save(entity);
}
#Override
public T updateById(T entity, ID entityId) {
Optional<T> optional = abstractBaseRepository.findById(entityId);
if (optional.isPresent()) {
return (T) abstractBaseRepository.save(entity);
} else {
return null;
}
}
#Override
public void delete(T entity) {
abstractBaseRepository.delete(entity);
}
#Override
public void deleteById(ID entityId) {
abstractBaseRepository.deleteById(entityId);
}
GenericRepository:
I created a basic repository class and because I needed a custom Query that was not supported by jpa, I had to add two more classes.
#Repository
public interface AbstractBaseRepository<T extends AbstractBaseEntity, ID extends Serializable> extends JpaRepository<T, ID>,AbstractBaseRepositoryCustom {
}
public interface AbstractBaseRepositoryCustom<T extends AbstractBaseEntity,ID extends Serializable> {
List<T> findAllByFiter(String textQuery);
}
public class AbstractBaseRepositoryCustomImpl<T extends AbstractBaseEntity,ID extends Serializable> implements AbstractBaseRepositoryCustom {
#PersistenceContext
EntityManager entityManager;
#Override
public List findAllByFiter(String textQuery) {
Query query=entityManager.createQuery(textQuery);
return query.getResultList();
}
}
I don't know that the methods and wiring in my service class are correct. Is the repository installed correctly? Please help.
I am using Spring Boot 4, Hibernate and JPA annotations. I ran into this error
org.hibernate.PersistentObjectException: detached entity passed to
persist.
I tried searching through the internet and couldn't quite get the proper answer. I tried to use merge instead of persist and it did not work. Besides I think merge is used when updating a resource.
Here is my code:
`
package com.matome.users.login.stats.springbootStarter.model;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.NamedQueries;
import com.matome.users.login.stats.springbootStarter.userconstants.UserRepositoryConstants;
#NamedQueries({
#NamedQuery(name = UserRepositoryConstants.NAME_GET_ALL_USERS,
query = UserRepositoryConstants.QUERY_GET_ALL_USERS),
#NamedQuery(name = UserRepositoryConstants.NAME_GET_USER_BY_ID,
query = UserRepositoryConstants.QUERY_GET_USER_BY_ID),
#NamedQuery(name = UserRepositoryConstants.NAME_GET_USER_BY_USERNAME,
query = UserRepositoryConstants.QUERY_GET_USER_BY_USERNAME),
#NamedQuery(name = UserRepositoryConstants.NAME_DELETE_USER,
query = UserRepositoryConstants.QUERY_DELETE_USER)
})
#Entity
public class User implements Serializable{
private static final long serialVersionUID = 1L;
#Id #GeneratedValue(strategy = GenerationType.AUTO)
Long id;
String username;
String password;
String phone;
public Long getId() {
return id;
}
public void setId(Long 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;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
`
CRUD service
`
package com.matome.users.login.stats.springbootStarter.CRUDService;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.matome.users.login.stats.springbootStarter.model.User;
import com.matome.users.login.stats.springbootStarter.repository.UserRepository;
#Service
#Transactional
public class UserCRUDService {
#Autowired
UserRepository userRepository;
public User createUser(User user) {
userRepository.save(user);
return user;
}
public User updateUser(User user) {
User existingUser = userRepository.getUserById(user.getId());
if (existingUser == null) {
throw new NoResultException();
}
existingUser.setId(user.getId());
existingUser.setUsername(user.getUsername());
existingUser.setPhone(user.getPhone());
existingUser.setPassword(user.getPassword());
userRepository.update(user);
return user;
}
public User deleteUser(long id) {
User user = userRepository.getUserById(id);
if (user == null) {
throw new NoResultException();
}
userRepository.delete(user);
return user;
}
}
`
Repository
`
package com.matome.users.login.stats.springbootStarter.repository;
import java.util.List;
import javax.persistence.TypedQuery;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.matome.users.login.stats.springbootStarter.model.User;
import com.matome.users.login.stats.springbootStarter.userconstants.UserRepositoryConstants;
#Repository
#Transactional
public class UserRepository extends AbstractRepository<User> {
TypedQuery<User> query;
public List<User> getAllUsers() {
query = entityManager.createNamedQuery(UserRepositoryConstants.NAME_GET_ALL_USERS, User.class);
return query.getResultList();
}
public User getUserById(long id) {
query = entityManager.createNamedQuery(UserRepositoryConstants.NAME_GET_USER_BY_ID, User.class);
query.setParameter("id", id);
return query.getSingleResult();
}
public User getUserByUsername(String username) {
query = entityManager.createNamedQuery(UserRepositoryConstants.NAME_GET_USER_BY_USERNAME, User.class);
query.setParameter("username", username);
return query.getSingleResult();
}
}
`
Abstract repository
`
package com.matome.users.login.stats.springbootStarter.repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public abstract class AbstractRepository<T> {
#PersistenceContext
protected EntityManager entityManager;
#Transactional(propagation = Propagation.MANDATORY)
public void save(T entity) {
entityManager.persist(entity);
entityManager.flush();
entityManager.refresh(entity);
}
#Transactional
public T update(T entity) {
return entityManager.merge(entity);
}
#Transactional
public void delete(T entity) {
entityManager.remove(entity);
}
}
`
Factory
`
package com.matome.users.login.stats.springbootStarter.factories;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.matome.users.login.stats.springbootStarter.BackingForm.UserBackingForm;
import com.matome.users.login.stats.springbootStarter.model.User;
import com.matome.users.login.stats.springbootStarter.viewmodels.UserViewModel;
#Service
public class UserFactory {
public UserViewModel createViewModel(User user) {
UserViewModel viewModel = new UserViewModel();
viewModel.setId(user.getId());
viewModel.setUsername(user.getUsername());
viewModel.setPhone(user.getPhone());
return viewModel;
}
public List<UserViewModel> createVewModels(List<User> users) {
List<UserViewModel> viewModels = new ArrayList<>();
if (users != null) {
for (User user : users) {
viewModels.add(createViewModel(user));
}
}
return viewModels;
}
public User createEntity(UserBackingForm userBackingForm) {
User user = new User();
user.setId(userBackingForm.getId());
user.setUsername(userBackingForm.getUsername());
user.setPassword(userBackingForm.getPassword());
user.setPhone(userBackingForm.getPhone());
return user;
}
}
`
Backing form
`
package com.matome.users.login.stats.springbootStarter.BackingForm;
public class UserBackingForm {
private long id;
private String username;
private String password;
private String phone;
public long getId() {
return id;
}
public void setId(long 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;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
`
Controller
`
package com.matome.users.login.stats.springbootStarter.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.matome.users.login.stats.springbootStarter.BackingForm.UserBackingForm;
import com.matome.users.login.stats.springbootStarter.CRUDService.UserCRUDService;
import com.matome.users.login.stats.springbootStarter.factories.UserFactory;
import com.matome.users.login.stats.springbootStarter.model.User;
import com.matome.users.login.stats.springbootStarter.repository.UserRepository;
import com.matome.users.login.stats.springbootStarter.viewmodels.UserViewModel;
#RestController
#RequestMapping("api")
public class UserController {
#Autowired
private UserRepository userRepository;
#Autowired
private UserCRUDService userCRUDService;
#Autowired
private UserFactory userFactory;
#RequestMapping(value = "/users", method = RequestMethod.GET)
public Map<String, Object> getAllUsers() {
Map<String, Object> map = new HashMap<>();
map.put("users", userRepository.getAllUsers());
return map;
}
#RequestMapping(value = "{id}", method = RequestMethod.GET)
public Map<String, Object> getUserById(#PathVariable long id) {
Map<String, Object> map = new HashMap<>();
map.put("user", userRepository.getUserById(id));
return map;
}
#RequestMapping(value = "/user/add", method = RequestMethod.POST)
public UserViewModel addUser(#RequestBody UserBackingForm form) {
User user = userFactory.createEntity(form);
//user.setIsActive(true);
user = userCRUDService.createUser(user);
return userFactory.createViewModel(user);
}
#RequestMapping(value = "/user/add/{id}", method = RequestMethod.PUT)
public Map<String, Object> updateUser(#PathVariable("id") Long id, #RequestBody User user) {
Map<String, Object> map = new HashMap<>();
user.setId(id);
map.put("updatedUser", userCRUDService.updateUser(user));
return map;
}
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String testFinal() {
return "User test sucessfully";
}
#RequestMapping(value = "{id}", method = RequestMethod.DELETE)
public void deleteUser(#PathVariable("id") Long id) {
User user = userRepository.getUserById(id);
userRepository.delete(user);
}
}
`
View Model
`
package com.matome.users.login.stats.springbootStarter.viewmodels;
public class UserViewModel extends BaseViewModel<Long> {
private String username;
private String phone;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
`
base ViewModel
`
package com.matome.users.login.stats.springbootStarter.viewmodels;
public abstract class BaseViewModel<T> {
private T id;
public T getId() {
return id;
}
public void setId(T id) {
this.id = id;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
#SuppressWarnings("unchecked")
BaseViewModel<T> other = (BaseViewModel<T>) obj;
if (getId() == null) {
if (other.getId() != null)
return false;
} else if (!getId().equals(other.getId()))
return false;
return true;
}
}
`
Main
`
package com.matome.users.login.stats.springbootStarter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Main { public static void main(String[] args) {
SpringApplication.run(Main.class,args);
}
}
`
Stack Trace
`
org.hibernate.PersistentObjectException: detached entity passed to persist: com.matome.users.login.stats.springbootStarter.model.User
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:124) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:775) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:748) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:753) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1146) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_131]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_131]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_131]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_131]
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:347) ~[spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at com.sun.proxy.$Proxy78.persist(Unknown Source) ~[na:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_131]
`
In my opinion the problem lies in the fact that you have declared the id of User entity as auto-generated:
#Id #GeneratedValue(strategy = GenerationType.AUTO)
Long id;
and when you create the User object you set the id manually:
public User createEntity(UserBackingForm userBackingForm) {
User user = new User();
user.setId(userBackingForm.getId());
...
and thus when you pass that to persist / save, the Persistence Provider will not allow that as it expects that the id will not be set by the client.
The bottom line is that you should not set the id regarding the current configuration when you are aiming at persisting a new entry and you should be fine.
Can you provide a full stack trace ?
Some hints for you :
You should only set #Transactional at Service level, not dao / repository.
Entitymanager refresh method means get data from DB to recreate an entity. If you just persist the entity, there is no need to refresh it.
#Transactional(propagation = Propagation.MANDATORY)
public void save(T entity) {
entityManager.persist(entity);
entityManager.flush(); // This is not really usefull on a small transaction
entityManager.refresh(entity); // No need for that : persist means entity already in sync
}
On update side, if you have a managed entity there is no need to sync it back to the DB, JPA take care of that automatically. (and merge push entity in the db forcing an update of all column) :
public User updateUser(User user) {
User existingUser = userRepository.getUserById(user.getId());
// ...
existingUser.setPassword(user.getPassword());
// ...
userRepository.update(user); // This is not needed !
return user;
}
If you want simplest things, you should take a look at lombok. It will help you write simpliest bean ( #Data to generate getter/setter ...)
You use Spring, so take also a look at spring-Data-JPA, It writes for you some cumberstone code.
I have a situation where i need to reset the auto-Increment key to the previous value in case of roll back. I have a series of create statement in my controller class. If any exception occurs I am able to roll back all the create statements. But the the auto-Increment generated doesn't reset for a particular successfully entity. Kindly help
The following is my contoller class.
#EJB
private jpa.session.ClassMasterFacade ejbFacadeCM;
#EJB
private jpa.session.StudentMasterFacade ejbFacadeSM;
#EJB
private jpa.session.ParentsMasterFacade ejbFacadePM;
#EJB
private jpa.session.AddressMasterFacade ejbFacadeAM;
#Resource
UserTransaction tran;
public String confirmData() {
try {
tran.begin();
ejbFacadeSM;.create(selectedSM);
ejbFacadeCM;.create(selectedCM)
ejbFacadeAM;.create(selectedAM);
ejbFacadePM;.create(selectedPM);
} catch (Exception e) {
tran.rollback();
JsfUtil.addErrorMessage(e, ResourceBundle.getBundle ("/resources/Bundle").getString("PersistenceErrorOccured"));
return null;
}
}
Example Facade Class
#Stateless
public class ClassMasterFacade extends AbstractFacade<ClassMaster> {
#PersistenceContext(unitName = "sdjv_smsPU")
private EntityManager em;
#Override
protected EntityManager getEntityManager() {
return em;
}
public ClassMasterFacade() {
super(ClassMaster.class);
}
}
All other facade classes are similar to the above class. The abstract class is:
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));
}
public T find(Object id) {
return getEntityManager().find(entityClass, id);
}
}
The autogenerated key in each enityclass is similar to
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "class_group_id")
private Integer classGroupId;
First of all, sorry for my english.
Im making, for a friend, a desktop application with JPA(EclipseLink) that access a SQLite database.
I already created the database and the entities in Eclipse. But i also created a class called UniversalDAO which is an utility class used by all the entities to access and persist the database:
package model.DAO;
import java.util.ArrayList;
import javax.persistence.*;
import model.entities.Entities;
public class UniversalDAO {
private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("TheDatabase");
private static EntityManager em = emf.createEntityManager();
private UniversalDAO (){}
public static void close(){
em.close();
emf.close();
}
public static Entities getOne(Class<? extends Entities> table, Object primaryKey) {
return em.find(table, primaryKey);
}
public static ArrayList<Entities> getAll(Class<? extends Entities> table) {
ArrayList<Entities> ret = new ArrayList<>();
for(Object obj : em.createQuery("SELECT o FROM " + table.getName() + " o").getResultList())
ret.add((Entities) obj);
return ret;
}
public static ArrayList<Entities> getWithCondition(Class<? extends Entities> table, String condition) {
ArrayList<Entities> ret = new ArrayList<>();
for(Object obj : em.createQuery("SELECT o FROM " + table.getName() + " o WHERE " + condition).getResultList())
ret.add((Entities) obj);
return ret;
}
public static void insert(Entities row) {
em.getTransaction().begin();
em.persist(row);
em.flush();
em.getTransaction().commit();
}
public static void update(Entities row) {
em.getTransaction().begin();
em.merge(row);
em.flush();
em.getTransaction().commit();
}
public static void delete(Class<? extends Entities> table, Object primaryKey) {
em.getTransaction().begin();
Entities row = em.find(table, primaryKey);
em.remove(row);
em.flush();
em.getTransaction().commit();
}
}
To group all the entites and use them in this class i created an empty interface called Entities.
This is how one of the entities looks like:
package model.entities;
import java.util.ArrayList;
import javax.persistence.*;
#Entity
#Table(name="emails")
public class EntityEmail implements Entities {
#Id
#Column(name="id_email")
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private int idEmail;
#Column(name="email")
private String email;
#Column(name="description")
private String description;
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name="people_emails",
joinColumns=#JoinColumn(name="id_email", referencedColumnName="id_email"),
inverseJoinColumns=#JoinColumn(name="id_person", referencedColumnName="id_person"))
private ArrayList<EntityPerson> people;
public EntityEmail() {
}
public int getIdEmail() {
return this.idEmail;
}
public void setIdEmail(int idEmail) {
this.idEmail = idEmail;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public ArrayList<EntityPerson> getPeople() {
return people;
}
public void setPeople(ArrayList<EntityPerson> people) {
this.people = people;
}
}
Like you can appreciate im not a professional and i still have a lot to learn.
So, i was wondering if this approach is correct or if i should have one DAO for each entity.
It looks like you try to invent Generic DAO pattern. If so, you're essentially on the right way.
Generic DAO pattern works as follows:
Create a generic base class that all your DAOs will extend:
public abstract class GenericDao<E, ID extends Serializable> {
...
// Implement common operations that are relevant to all entities here:
public E findById(ID id) { ... }
public void save(E entity) { ... }
// etc
...
}
Create concrete DAO implementations by extending GenericDao:
public class EntityEmailDao extends GenericDao<EntityEmail, Integer> {
// This class may contain operations that are relevant to specific entity:
public E findByEmail(String email) { ... }
}
Since GenericDao is generic, you don't need to make your entities extend any common interface
There are plenty of exisiting implementations of this pattern around, take a look, for example, here.
I am running a RESTful web service on Glassfish 3.1.2 server. I use Jersey as JAX-RS implementation, Jackson as it's JSON provider, and (JPA) EclipseLink 2.5 as persistence provider for MySQL.
I'll introduce some context and then ask my question.
Let's assume we have the following hierarchy: Item entity and CraftableItem entity that inherits it (see code samples below). They both do have corresponding EAO and Resource classes: ItemEAO, CraftableItemEAO + ItemResource, CraftableItemResource (see code samples below).
Let's have two records in items table:
id | name
1 | "craftable"
2 | "non craftable"
and one corresponding record for "craftable" in crafting_items_joined:
id | crafting_time
1 | 120000
I want to get as more specific information about each entity from GET /item request as possible. I want it to return [{"id":1,"name":"craftable", "crafting_time":120000}, {"id":2,"name":"non craftable"}]. At the momment, GET /item/1 returns {"id":1,"name":"craftable", "crafting_time":120000} and GET /item/2 returns just {"id":2,"name":"non craftable"} which is the behavior i want, but for GET /item. Getting all information about entity present.
Code samples (setters are omitted for sanity's sake).
#MappedSuperclass
public abstract class BaseEntity {
protected Long id;
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
}
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "items")
public class Item extends BaseEntity implements Serializable {
private String name;
#Column(name = "name", nullable = false, unique = true)
public String getName() {
return name;
}
}
#Entity
#DiscriminatorValue("craftable")
#Table(name = "craftable_items_joined")
public class CraftableItem extends Item {
protected long craftingTime;
#Column(name = "crafting_time", nullable = false)
public long getCraftingTime() {
return craftingTime;
}
}
EAO then:
public abstract class AbstractEAO<T> {
private Class<T> entityClass;
public AbstractEAO(Class<T> entityClass) {
this.entityClass = entityClass;
}
public Class<T> getEntityClass() {
return entityClass;
}
protected abstract EntityManager getEntityManager();
// create, edit, remove
public T find(Object id) {
return getEntityManager().find(entityClass, id);
}
public List<T> findAll() {
CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
return getEntityManager().createQuery(cq).getResultList();
}
}
#Stateless
#LocalBean
public class ItemEAO extends AbstractEAO<Item> {
#PersistenceContext(unitName = "primary")
private EntityManager entityManager;
public ItemEAO() {
super(Item.class);
}
#Override
protected EntityManager getEntityManager() {
return entityManager;
}
}
#Stateless
#LocalBean
public class CraftableItemEAO extends AbstractEAO<CraftableItem> {
#PersistenceContext(unitName = "primary")
private EntityManager entityManager;
public CraftableItemEAO() {
super(CraftableItem.class);
}
#Override
protected EntityManager getEntityManager() {
return entityManager;
}
}
Resource finally:
public abstract class AbstractResource<T extends BaseEntity> {
protected abstract AbstractEAO<T> getEAO();
#GET
public List<T> findAll() {
return getEAO().findAll();
}
#GET
#Path("{id}")
public T find(#PathParam("id") Long id) {
return getEAO().find(id);
}
// create, edit, remove
}
#Stateless
#Path("item")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class ItemResource extends AbstractResource<Item> {
#EJB private ItemEAO itemEAO;
#Override
protected AbstractEAO<Item> getEAO() {
return itemEAO;
}
}
#Stateless
#Path("item/craftable")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class CraftableItemResource extends AbstractResource<CraftableItem> {
#EJB private CraftableItemEAO craftableItemEAO;
#Override
protected AbstractEAO<CraftableItem> getEAO() {
return craftableItemEAO;
}
}