Spring Data JPA Specification using CriteriaBuilder with a one to many relationship - java

I have a User entity, a UserToApplication entity, and an Application entity.
A single User can have access to more than one Application. And a single Application can be used by more than one User.
Here is the User entity.
#Entity
#Table(name = "USER", schema = "UDB")
public class User {
private Long userId;
private Collection<Application> applications;
private String firstNm;
private String lastNm;
private String email;
#SequenceGenerator(name = "generator", sequenceName = "UDB.USER_SEQ", initialValue = 1, allocationSize = 1)
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")
#Column(name = "USER_ID", unique = true, nullable = false)
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
public Collection<Application> getApplications() {
return applications;
}
public void setApplications(Collection<Application> applications) {
this.applications = applications;
}
/* Other getters and setters omitted for brevity */
}
Here is the UserToApplication entity.
#Entity
#Table(name = "USER_TO_APPLICATION", schema = "UDB")
public class Application {
private Long userToApplicationId;
private User user;
private Application application;
#SequenceGenerator(name = "generator", sequenceName = "UDB.USER_TO_APP_SEQ", initialValue = 0, allocationSize = 1)
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")
#Column(name = "USER_TO_APPLICATION_ID", unique = true, nullable = false)
public Long getUserToApplicationId() {
return userToApplicationId;
}
public void setUserToApplicationId(Long userToApplicationId) {
this.userToApplicationId = userToApplicationId;
}
#ManyToOne
#JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID", nullable = false)
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#ManyToOne
#JoinColumn(name = "APPLICATION_ID", nullable = false)
public Application getApplication() {
return application;
}
}
And here is the Application entity.
#Entity
#Table(name = "APPLICATION", schema = "UDB")
public class Application {
private Long applicationId;
private String name;
private String code;
/* Getters and setters omitted for brevity */
}
I have the following Specification that I use to search for a User by firstNm, lastNm, and email.
public class UserSpecification {
public static Specification<User> findByFirstNmLastNmEmail(String firstNm, String lastNm, String email) {
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
final Predicate firstNmPredicate = null;
final Predicate lastNmPredicate = null;
final Predicate emailPredicate = null;
if (!StringUtils.isEmpty(firstNm)) {
firstNmPredicate = cb.like(cb.lower(root.get(User_.firstNm), firstNm));
}
if (!StringUtils.isEmpty(lastNm)) {
lastNmPredicate = cb.like(cb.lower(root.get(User_.lastNm), lastNm));
}
if (!StringUtils.isEmpty(email)) {
emailPredicate = cb.like(cb.lower(root.get(User_.email), email));
}
return cb.and(firstNmPredicate, lastNmPredicate, emailPredicate);
}
};
}
}
And here is the User_ metamodel that I have so far.
#StaticMetamodel(User.class)
public class User_ {
public static volatile SingularAttribute<User, String> firstNm;
public static volatile SingularAttribute<User, String> lastNm;
public static volatile SingularAttribute<User, String> email;
}
Now, I would like to also pass in a list of application IDs to the Specification, such that its method signature would be:
public static Specification<User> findByFirstNmLastNmEmailApp(String firstNm, String lastNm, String email, Collection<Long> appIds)
So, my question is, if I add the #OneToMany mapping to the User_ metamodel for the Collection<Application> applications field of my User entity, then how would I reference it in the Specification?
My current Specification would be similar to the following SQL query:
select * from user u
where lower(first_nm) like '%firstNm%'
and lower(last_nm) like '%lastNm%'
and lower(email) like '%email%';
And what I would like to achieve in the new Specification would be something like this:
select * from user u
join user_to_application uta on uta.user_id = u.user_id
where lower(u.first_nm) like '%firstNm%'
and lower(u.last_nm) like '%lastNm%'
and lower(u.email) like '%email%'
and uta.application_id in (appIds);
Is it possible to do this kind of mapping in the metamodel, and how could I achieve this result in my Specification?

I found a solution. To map a one to many attribute, in the metamodel I added the following:
public static volatile CollectionAttribute<User, Application> applications;
I also needed to add a metamodel for the Application entity.
#StaticMetamodel(Application.class)
public class Application_ {
public static volatile SingularAttribute<Application, Long> applicationId;
}
Then in my Specification, I could access the applications for a user, using the .join() method on the Root<User> instance. Here is the Predicate I formed.
final Predicate appPredicate = root.join(User_.applications).get(Application_.applicationId).in(appIds);
Also, it is worth noting that my Specification as it is written in the question will not work if any of the input values are empty. A null Predicate passed to the .and() method of CriteriaBuilder will cause a NullPointerException. So, I created an ArrayList of type Predicate, then added each Predicate to the list if the corresponding parameter was non-empty. Finally, I convert the ArrayList to an array to pass it to the .and() function of the CriteriaBuilder. Here is the final Specification:
public class UserSpecification {
public static Specification<User> findByFirstNmLastNmEmailApp(String firstNm, String lastNm, String email, Collection<Long> appIds) {
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
final Collection<Predicate> predicates = new ArrayList<>();
if (!StringUtils.isEmpty(firstNm)) {
final Predicate firstNmPredicate = cb.like(cb.lower(root.get(User_.firstNm), firstNm));
predicates.add(firstNmPredicate);
}
if (!StringUtils.isEmpty(lastNm)) {
final Predicate lastNmPredicate = cb.like(cb.lower(root.get(User_.lastNm), lastNm));
predicates.add(lastNmPredicate);
}
if (!StringUtils.isEmpty(email)) {
final Predicate emailPredicate = cb.like(cb.lower(root.get(User_.email), email));
predicates.add(emailPredicate);
}
if (!appIds.isEmpty()) {
final Predicate appPredicate = root.join(User_.applications).get(Application_.applicationId).in(appIds);
predicates.add(appPredicate);
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
}
}

Related

Spring Data JPA Many to Many Service Repository Problem

I am trying to add ManyToMany entity to my application. I created entity but cannot implement it.
Actor class
#Entity
#Table(name = "actor")
public class Actor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false, name = "actor_name")
private String actorName;
#ManyToMany(mappedBy = "actor", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Movie> movie = new HashSet<Movie>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getActorName() { return actorName; }
public void setActorName(String actorName) {
this.actorName = actorName;
}
public Set<Movie> getMovie() {
return movie;
}
public void setMovie(Set<Movie> movie) {
this.movie = movie;
}
}
In movie class I have
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "movie_actor",
joinColumns = {#JoinColumn(name = "movie_id")},
inverseJoinColumns = {#JoinColumn(name = "actor_id")}
)
Set<Actor> actor = new HashSet<Actor>();
........................
public Set<Actor> getActor () {
return actor;
}
public void setActor(Set<Actor> actor){
this.actor = actor;
}
I created my entity just like this but in MovieService;
Actor actor = ActorRepository.findByActorName(movie.getActor().getActorName());
movie.setActor(actor);
This part gives me error. movie.getActor().getActorName() method cannot find. Where do I need to look? In IDE it also says method getActorName and setActorName is never used. I am also adding my ActorRepository and ActorService to closer look to the problem.
ActorRepository
public interface ActorRepository extends JpaRepository<Actor, Integer> {
Set<Actor> findByActorName(String actorName);
}
ActorService
#Service
public class ActorService {
private ActorRepository actorRepository;
#Autowired
public ActorService(ActorRepository actorRepository) {
this.actorRepository = actorRepository;
}
public List<Actor> getAllActor() {
return actorRepository.findAll();
}
}
After adding ManyToMany I was using is as OneToMany entity. Services is works for OneToMany. How can I use them for ManyToMany? I need to add multiple actors to my movies. I couldn't find MVC projects for ManyToMany implementation.
You're invoking movie.getActor().getActorName() which basically does a getActorName() on a Set<Actor> object.
You're basically treating the relation as a ManyToOne instead of a OneToMany
You could use the following to fetch the first Actor of the Set
ActorRepository.findByActorName(movie.getActors().iterator().next().getActorName());
But then of course, you don't have all your Actor's names
What you could do is the following
public interface ActorRepository extends JpaRepository<Actor, Integer> {
Set<Actor> findByActorNameIn(List<String> actorName);
}
And invoke it that way
ActorRepository.findByActorNameIn(
movie.getActors()
.stream()
.map(Actor::getName)
.collect(Collectors.toList())
);

crudrepository findBy method signature for list of tuples

I have an Entity Class like this:
#Entity
#Table(name = "CUSTOMER")
class Customer{
#Id
#Column(name = "Id")
Long id;
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
How to write findBy method for the below query using crudrepository spring data jpa?
select * from customer where (email, mobile) IN (("a#b.c","8971"), ("e#f.g", "8888"))
I'm expecting something like
List<Customer> findByEmailMobileIn(List<Tuple> tuples);
I want to get the list of customers from given pairs
I think this can be done with org.springframework.data.jpa.domain.Specification. You can pass a list of your tuples and proceed them this way (don't care that Tuple is not an entity, but you need to define this class):
public class CustomerSpecification implements Specification<Customer> {
// names of the fields in your Customer entity
private static final String CONST_EMAIL_ID = "emailId";
private static final String CONST_MOBILE = "mobile";
private List<MyTuple> tuples;
public ClaimSpecification(List<MyTuple> tuples) {
this.tuples = tuples;
}
#Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// will be connected with logical OR
List<Predicate> predicates = new ArrayList<>();
tuples.forEach(tuple -> {
List<Predicate> innerPredicates = new ArrayList<>();
if (tuple.getEmail() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_EMAIL_ID), tuple.getEmail()));
}
if (tuple.getMobile() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_MOBILE), tuple.getMobile()));
}
// these predicates match a tuple, hence joined with AND
predicates.add(andTogether(innerPredicates, cb));
});
return orTogether(predicates, cb);
}
private Predicate orTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.or(predicates.toArray(new Predicate[0]));
}
private Predicate andTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.and(predicates.toArray(new Predicate[0]));
}
}
Your repo is supposed to extend interface JpaSpecificationExecutor<Customer>.
Then construct a specification with a list of tuples and pass it to the method customerRepo.findAll(Specification<Customer>) - it returns a list of customers.
It is maybe cleaner using a projection :
#Entity
#Table(name = "CUSTOMER")
class CustomerQueryData {
#Id
#Column(name = "Id")
Long id;
#OneToOne
#JoinColumns(#JoinColumn(name = "emailId"), #JoinColumn(name = "mobile"))
Contact contact;
}
The Contact Entity :
#Entity
#Table(name = "CUSTOMER")
class Contact{
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
After specifying the entities, the repo :
CustomerJpaProjection extends Repository<CustomerQueryData, Long>, QueryDslPredicateExecutor<CustomerQueryData> {
#Override
List<CustomerQueryData> findAll(Predicate predicate);
}
And the repo call :
ArrayList<Contact> contacts = new ArrayList<>();
contacts.add(new Contact("a#b.c","8971"));
contacts.add(new Contact("e#f.g", "8888"));
customerJpaProjection.findAll(QCustomerQueryData.customerQueryData.contact.in(contacts));
Not tested code.

JPA: left join without #OneToMany annotations

I have a OneToMany relationship in my DB but I don't want that Hibernate manages it directly.
This relationships are translations, but a DTO represents itself a translated registry:
#Entity
#Table(name = "my_table")
public class MyTable {
#Id
#Column(name = "id", nullable = false, unique = true)
private Integer id;
#Transient
private String lang;
#Transient
private String text;
// getters and setters
...
}
#Entity
#Table(name = "my_table_translation")
public class MyTableTranslation {
#Id
#Column(name = "id", nullable = false, unique = false)
private Integer id;
#Id
#Column(name = "lang", nullable = false, unique = false, length = 2)
private String lang;
#Column(name = "text", nullable = false, unique = false, length = 200)
private String text;
// getters and setters
...
}
I want to have an specific findAll(String lang) method with a lang parameter, and use an Specification Criteria to build the query. Something like that:
public void findAll(String language) {
List<MyTable> list = repository.findAll(new Specification<MyTable>() {
#Override
public Predicate toPredicate(Root<MyTable> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// something there
return ...;
}
});
}
The fact is that I don't know how to do that, because I can't use JOIN clause, as I have not an attribute in the model that represents the relationship.
I tried to use the SELECT...FROM...LEFT JOIN query with SQL notation,
SELECT t1, t2 FROM MyTable t1 LEFT JOIN MyTableTranslation t2 ON t1.id = t2.id
and it works, but not as desired. The resulting list of objects, is a list of 2 object per item: one is the MyTable object, and the other is the MyTableTranslation related object. I need to parse the list and programatically build the objects using PropertyUtils class from Apache Commons library.
It is not clean I think... Does anybody know how to make it easy, without using SQL notation?
Marc, you can do the following to make it work and you do not need complicated join clauses or predicate right now. A simple implementation in embedded H2 database and JUnit testing will be sufficient for proof of concept (POC) as below
NOTE:
I am using Spring + Plain JPA with Hibernate implementation for POC.
I am using the Spring recommended way of managing transaction.
com.mycompany.h2.jpa package contains the entity classes.
Take a look at the mytable.sql which has similar structure to your needs.
MyTable.java
#Entity
#Table(name = "my_table")
public class MyTable implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id", nullable = false, unique = true)
#JoinColumn(referencedColumnName="id", insertable=true, updatable=false)
private Long id;
#Column(name = "lang", unique=true)
private String lang;
#Column(name = "text")
private String text;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name="id", insertable=true, updatable=true, referencedColumnName="id")
private List<MyTableTranslation> translations = new ArrayList<MyTableTranslation>();
...
// getters and setters, toString()
}
MyTableTranslation.java
#Entity
#Table(name = "my_table_translation")
public class MyTableTranslation implements Serializable {
private static final long serialVersionUID = 11L;
#Id
#Column(name = "id")
private Long id;
#Id
#Column(name = "speaker")
String speaker;
...
// getters and setters, toString()
}
TestH2DatabaseConfiguration.java
#Configuration
#EnableTransactionManagement
public class TestH2DatabaseConfiguration {
final static Logger log = LoggerFactory.getLogger(TestH2DatabaseConfiguration.class);
#Bean
#Qualifier("dataSource")
public DataSource h2DataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).addScript("classpath:mytable.sql").build();
}
#Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(h2DataSource());
factoryBean.setJpaVendorAdapter(jpaVendorAdapter);
factoryBean.setPackagesToScan("com.mycompany.h2.jpa");
factoryBean.setPersistenceUnitName("my_table");
Properties prop = new Properties();
prop.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
prop.put("hibernate.show_sql", "true");
prop.put("hibernate.hbm2ddl.auto", "none");
factoryBean.setJpaProperties(prop);
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
return txManager;
}
#Bean
public MyTableDAO myTableDAO() {
return new MyTableDAOJPAImpl();
}
#Bean
public MyTableServiceImpl myTableService() {
MyTableServiceImpl myTableService = new MyTableServiceImpl();
myTableService.setMyTableDAO(myTableDAO());
return myTableService;
}
}
MyTableService.java
public interface MyTableService {
public MyTable saveMyTableTranslation(MyTable myTable);
public List<MyTable> getAllMyTables();
public MyTable getMyTable(Long entityId);
public MyTable getMyTable(String lang);
}
MyTableServiceImpl.java
#Transactional
public class MyTableServiceImpl implements MyTableService {
final static Logger log = LoggerFactory.getLogger(MyTableServiceImpl.class);
private MyTableDAO myTableDAO;
public void setMyTableDAO(MyTableDAO myTableDAO) {
this.myTableDAO = myTableDAO;
}
public MyTable saveMyTableTranslation(MyTable myTable) {
return myTableDAO.saveMyTableTranslation(myTable);
}
public List<MyTable> getAllMyTables() {
return myTableDAO.getAllMyTables();
}
public MyTable getMyTable(Long entityId) {
return myTableDAO.getMyTable(entityId);
}
public MyTable getMyTable(String lang) {
return myTableDAO.getMyTable(lang);
}
}
MyTableDAO.java
public interface MyTableDAO {
public MyTable saveMyTableTranslation(MyTable myTable);
public List<MyTable> getAllMyTables();
public MyTable getMyTable(Long entityId);
public MyTable getMyTable(String lang);
}
MyTableDAOJPAImpl.java
public class MyTableDAOJPAImpl implements MyTableDAO {
final static Logger log = LoggerFactory.getLogger(MyTableDAOJPAImpl.class);
#PersistenceContext
private EntityManager entityManager;
public MyTable saveMyTableTranslation(MyTable myTable) {
entityManager.persist(myTable);
return myTable;
}
#SuppressWarnings("unchecked")
public List<MyTable> getAllMyTables() {
return (List<MyTable>) entityManager.createQuery("FROM MyTable").getResultList();
}
public MyTable getMyTable(Long entityId) {
return (MyTable) entityManager.createQuery("FROM MyTable m WHERE m.id = :id ").setParameter("id", entityId).getSingleResult();
}
public MyTable getMyTable(String lang) {
return (MyTable) entityManager.createQuery("FROM MyTable m WHERE m.lang = :lang ").setParameter("lang", lang).getSingleResult();
}
}
MyTableTest.java (a JUnit test class)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { TestH2DatabaseConfiguration.class }, loader = AnnotationConfigContextLoader.class)
public class MyTableTest extends AbstractTransactionalJUnit4SpringContextTests {
final static Logger log = LoggerFactory.getLogger(MyTableTest.class);
#Autowired
#Qualifier("myTableService")
MyTableService myTableService;
#Test
public void test() throws ParseException {
MyTable parent = new MyTable();
parent.setLang("Italian");
parent.setText("Fast...");
MyTableTranslation child = new MyTableTranslation();
child.setSpeaker("Liotta");
parent = myTableService.saveMyTableTranslation(parent);
log.debug("parent ID : " + parent.getId());
MyTable spanishTables= myTableService.getMyTable("Spanish");
List<MyTableTranslation> spanishTranslations = spanishTables.getTranslations();
log.debug("spanishTranslations SIZE : " + spanishTranslations.size());
for (MyTableTranslation myTableTranslation : spanishTranslations) {
log.debug("myTableTranslation -> : " + myTableTranslation);
}
}
}
mytable.sql
CREATE TABLE IF NOT EXISTS my_table (
id IDENTITY PRIMARY KEY,
lang VARCHAR UNIQUE,
text VARCHAR
);
delete from my_table;
INSERT INTO my_table VALUES (1, 'Spanish', 'Beautiful...');
INSERT INTO my_table VALUES (2, 'English', 'Great...');
INSERT INTO my_table VALUES (3, 'French', 'Romantic...');
CREATE TABLE IF NOT EXISTS my_table_translation (
id INTEGER,
speaker VARCHAR
);
delete from my_table_translation;
INSERT INTO my_table_translation VALUES (1, 'Eduardo');
INSERT INTO my_table_translation VALUES (1, 'Diego');
INSERT INTO my_table_translation VALUES (2, 'George');
INSERT INTO my_table_translation VALUES (3, 'Pierre');
I tried to use #OneToMany annotation, and I change my DTO:
#Entity
#Table(name = "my_table")
public class MyTable {
#Id
#Column(name = "id", nullable = false, unique = true)
private Integer id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "id", referencedColumnName = "id")
private List<MyTableTranslation> translations = new ArrayList<MyTableTranslation>();
// getters and setters
...
}
And changed the Criteria as:
public void findAll(String isolang) {
List<MyTable> list = repository.findAll(new Specification<MyTable>() {
#Override
public Predicate toPredicate(Root<MyTable> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Join<Object, Object> langJoin = root.join("translations", JoinType.LEFT);
return cb.equal(langJoin.get("lang"), isolang);
}
});
}
But the list has no items. If I change the FetchType to EAGER, the Predicate has no effect, I get all the languages. I don't know how to proceed now...

Repository Inheritance

I would like to create a repository which performs the basic CRUD operations.
Since I have different kind of photos (CompanyPhoto, CarPhoto, ..), I would prefer to make the JPA repository generic, but also the EJB service as well.
Here is my classes:
#Entity
#Inheritance
#DiscriminatorColumn(name = "DESCRIMINATOR")
#Table(name = "PHOTOS")
public abstract class Photo {
public Photo() {
}
public Photo(String fileName) {
this.fileName = fileName;
// this.file = file;
}
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "PHOTOS_SEQ")
#TableGenerator(name = "PHOTOS_SEQ", table = "SEQUENCE", pkColumnName = "SEQ_NAME", pkColumnValue = "PHOTOS_SEQ", valueColumnName = "SEQ_COUNT", allocationSize = 50)
#Column(nullable = false)
private long id;
#Column(length = 255)
#Size(min = 0, max = 255, message = "{Photo.description.size}")
protected String description;
#Column(nullable = false, length = 255)
#NotNull(message = "{Photo.fileName.notNull}")
#Size(min = 1, max = 255, message = "{Photo.fileName.size}")
protected String fileName;
// ...
#Entity
#DiscriminatorValue("C")
public class CarPhoto extends Photo {
public CarPhoto() {
}
public CarPhoto(String fileName) {
super.fileName = fileName;
}
#ManyToOne(cascade = { CascadeType.DETACH })
#JoinColumn(name = "CARID", nullable = false)
#NotNull
private Car car;
// ...
#Entity
#DiscriminatorValue("P")
public class PersonPhoto extends Photo {
public PersonPhoto() {
}
public PersonPhoto(String fileName) {
super.fileName = fileName;
}
#ManyToOne(cascade = { CascadeType.DETACH })
#JoinColumn(name = "PERSONID", nullable = false)
#NotNull
private Person person;
// ...
#Stateless
#LocalBean
public class PhotoRepository<E> {
// In this class I would like to do create, remove, update and some basic find //operations..
#PersistenceContext
private EntityManager em;
public PhotoRepository() {
}
PhotoRepository(EntityManager em) {
this.em = em;
}
#Override
public E create(E photo) {
em.persist(photo);
return photo;
}
#Override
public E modify(E photo)
{
Class<E> photoClass;
// QUESTION: How am I going to call the getId() method from the object of type E class?
em.find(photoClass, photo.getId()); // This will not work.. =(
E mergedPhoto = em.merge(photo);
return mergedPhoto;
}
// ...
I hope that you understand what I want to perform. A generic repository for different kind of classes which all inherit from the same baseclass. Can you give me some best practices examples?
=)
Best regards
Change the generics definition to say that E has to be any type that extends from Photo. Then you will be able to access methods of the Photo class on variables of type E
#Stateless
#LocalBean
public class PhotoRepository<E extends Photo> {
You can use the following method to retrieve the actual class.
public Class getEntityClass() {
ParameterizedType parameterizedType =
(ParameterizedType) getClass().getGenericSuperClass();
return (Class) parameterizedtype.getActualTypeArguments()[0];
}
Ir you are using spring you should also take a look at spring-data-jpa - it provides such generic repositories implementation.
You can get the Id from an Entity using,
entityManagerFactory.getPersistenceUnitUtil().getIdentifier(object);
Although you do not need to call find() before merge(), just call merge(), it will do the find if required.

Remote Access To Jpa Entity

I'm currently working on system that consists of Java Web app and C# client app. Web app has Java Web Service, which has method that returns entity object of Program class:
#WebMethod(operationName = "getProgram")
public Program getProgram(#WebParam(name = "macAddress") String macAddress){
Device device = DeviceManager.getInstance().getDevice(macAddress);
if(device != null){
return device.getProgram();
}
return null;
}
This return object of type Program which has many properties and relations:
#Entity
#Table(name = "PROGRAM", schema = "APP")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Program.getProgramsByWeather", query = "SELECT p FROM Program p WHERE p.weather = :weather")})
public class Program extends DbEntity implements Serializable {
private static final long serialVersionUID = 1L;
#JoinColumn(name = "LOGO_ID", referencedColumnName = "ID")
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch= FetchType.EAGER)
private Logo logo;
#JoinColumn(name = "WEATHER_ID", referencedColumnName = "ID")
#ManyToOne
private Weather weather;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "program", orphanRemoval = true)
private List<ProgramPlaylist> programPlaylistList = new ArrayList<>();
#OneToMany(cascade = CascadeType.ALL, mappedBy = "program", orphanRemoval = true)
private List<ProgramTicker> programTickerList = new ArrayList<>();
#Column(name = "UPDATED")
private boolean updated;
public Program() {
}
public Program(String name, AppUser owner) {
super(name, owner);
}
public Logo getLogo() {
return logo;
}
public void setLogo(Logo logo) {
this.logo = logo;
}
public Weather getWeather() {
return weather;
}
public void setWeather(Weather weather) {
this.weather = weather;
}
public boolean isUpdated() {
return updated;
}
public void setUpdated(boolean updated) {
this.updated = updated;
}
#XmlElement
public List<ProgramPlaylist> getProgramPlaylistList() {
return programPlaylistList;
}
#XmlElement
public List<ProgramTicker> getProgramTickerList() {
return programTickerList;
}
#Override
public String toString() {
return "Program[ id=" + getId() + " ]";
}
}
Client can get this object and accessing some properties in client app like program.name, which it inherits from DbEntity, but when i try to call something like this:
program.logo.name
client throws NullReferenceException.
Same exception occurs when i try to iterate over the elements of programPlaylistList ArrayList.
I'm assuming that the object itself that is passed through to client isn't fully loaded.
How can i solve this problem, please help?!
EDIT
Ok, so I printed out XML response that client get from service and its populated correctly, but for some reason object fields aren't populated and are mostly null.
Why is this occurring?
Bye default, the fetch strategy for #OneToMany annotations is LAZY, have you tried specifying it to EAGER like in the #oneToOne field (fetch= FetchType.EAGER)?

Categories

Resources