I've got a pretty straight-forward problem I think but I couldn't come up with a nice structure for the classes with Hibernate or find examples of best practices.
I've got one table test with a mapped class Test. On one page controlled by a bean I've got all the items in a list for administrative editing (AdminTest) and in another page the same items are listed but for normal users with things like radio-buttons to influence them (ShowTest) without altering the database.
Since Hibernate seem to insist that you specify a directly mapped class when you retrieve items with the addEntity-function (in my classes ending with Manager) it feels like I'm forced to do something like this:
class Test { ... }
class AdminTest extends Test { ... }
#ManagedBean
class AdminTestManager {
List<AdminTest> items;
}
class ShowTest extends Test { ... }
#ManagedBean
class ShowTestManager {
List<ShowTest> items;
}
Then have separate mapping for both AdminTest and ShowTest to the same table with the same fields. This is really bad since I need to update both mappings whenever I add a field to the table test.
It feels like I've missed something here, that you should really just need the one Test mapped to the table test then have some pretty inheritance mapping but those require some identifier like a column or one-to-one mapped table.
EDIT:
Another solution I've tried was having one mapping for Test, still having AdminTest and ShowTest extend that class, but I'm copying the getters with reflection to the extended classes.
EDIT 2:
I'll provide a "real world" scenario here to better explain what I'm asking. Lets say I want to make a quiz-engine with one page where you can fill in questions through input-fields and another which lists these questions with a radio-button by each item with just "yes" or "no".
The database-table would just be created like this:
CREATE TABLE question (id INT AUTO_iNCREMENT, text VARCHAR, correct_answer INT, primary key(id));
The classes that I think I would need are these:
class Question {
private int id;
private String text;
private int correctAnswer;
// Getters and setters.
}
class EditQuestion extends Question {
void updateItem() { ... }
void removeItem() { ... }
}
#ManagedBean(name = "edit_quiz")
#ViewScoped
class EditQuestionManager {
List<EditQuestion> items;
void addItem() { ... }
void init() {
items = (ArrayList<EditQuestion>) HibernateUtil.getSessionFactory().getCurrentSession().createSQLQuery("SELECT * FROM question").addEntity(EditQuestion.class)
.list();
}
}
class AnswerQuestion extends Question {
int radioButtonValue;
// Getters and setters.
}
#ManagedBean(name = "take_quiz")
#ViewScoped
class AnswerQuestionManager {
List<AnswerQuestion> items;
void calculateResults() { ... }
void init() {
items = (ArrayList<AnswerQuestion>) HibernateUtil.getSessionFactory().getCurrentSession().createSQLQuery("SELECT * FROM question").addEntity(AnswerQuestion.class).list();
}
}
Then for example the JSF-page displaying the quiz could be something like this:
<ui:repeat value="#{take_quiz.items}" var="question">
<div>#{question.text}</div>
<h:selectOneRadio value="#{question.radioButtonValue}" onchange="submit()">
<f:selectItem itemValue="1" itemLabel="Yes" />
<f:selectItem itemValue="0" itemLabel="No" />
</h:selectOneRadio>
</ui:repeat>
<h:commandLink action="#{take_quiz.calculateResults()}">Done</h:commandLink>
Now the problem, at-least as it appears to me, is that to make this sort of list for both the admin-view and user-view I need to make entries to map the table "question" to both AnswerQuestion and EditQuestion in the hbm.xml-file. At-least without using reflection (to copy all the getter-values from Question to a new instance of AnswerQuestion/EditQuestion) which sort of feel like a hack to me? I mean this doesn't seem too complicated?
SOLUTION: Instead of having the classes extend the mapped Hibernate-table classes I simply added a private property of their types. Also in the Manager-classes I loop through the items from the database after they've been retrieved and set the lists like this:
ArrayList<Question> tempItems = (ArrayList<Question>) HibernateUtil.getSessionFactory().getCurrentSession().createSQLQuery("SELECT * FROM question").addEntity(Question.class).list();
ArrayList<EditQuestion> items = ArrayList<EditQuestion>();
for(int i = 0; tempItems.size(); i++) {
EditQuestion item = new EditQuestion();
item.setQuestion(tempItems.get(i));
items.add(item);
}
I'm struggling to understand your question. Are you having trouble expressing Class inheritance for entities?
Have you considered using the import javax.persistence.Inheritance annotation on top of your Test class?
Example:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Test {
}
#Entity
public class AdminTest extends Test {
}
More here.
Related
I have a web component called Select. It's a drop down box in Vaadin framework.
I want to construct Select drop down boxes at start up and they are going to contain data from repositories from JPA in Spring Boot.
Here is my example code:
private <T extends UserLoggRepository> void addSelect(Select<Long> select, String placeHolder, T repository) {
List<UserLogg> userLoggers = repository.findAll();
List<Long> items = new ArrayList<Long>();
for(int i = 0; i < userLoggers.size(); i++) {
long value = userLoggers.get(i).getLoggerId();
if(items.contains(value) == false)
items.add(value);
}
if(items.contains((long) 0) == false)
items.add((long) 0);
select.setItems(items);
select.setPlaceholder(placeHolder);
}
This code works! But I have different entities and different repositories.
I have the entities:
UserLogg
DataLogg
CalibrationLogg
AlarmLogg
And I have the repositories:
UserLoggRepository
DataLoggRepository
CalibrationLoggRepository
AlarmLoggRepository
Question:
How can I change the method addSelect so they will work with any respository that are connected to an entity? All repositories have the standard function findAll() and all entities have the field loggerId.
Focus:
What I have problem with is that this code only works for the entity UserLogg and repository UserLoggRepository.
List<UserLogg> userLoggers = repository.findAll();
What need to be done:
This need to change, but I don't know what to write here so it become more general for them all.
private <T extends UserLoggRepository> void addSelect
Update 1:
A repository header in JPA looks e.g like this:
public interface AlarmLoggRepository extends JpaRepository<AlarmLogg, Long>
I could use this, but still, got the UserLogg class is fixed.
private <T extends JpaRepository<UserLogg, Long>> void addSelect(Select<Long> select, String placeHolder, T repository)
If I understood your problem correctly, you can do inheritance and make a parent class for all your existing entities and change the definition of your addSelect method to return that parent class which could eventually return a type of any of it's subclasses.
For example, looks like your existing entities are all logs, so you can have an Abstract class named LoggRepository and have it extended by all your existing entities like:
public class UserLoggRepository extends LoggRepository
And then, update the method addSelect like below so it can return and of the subclasses of your parent abstract class LoggRepository
private <? extends LoggRepository> void addSelect(Select<Long> select, String placeHolder, T repository)
Hope this helps :)
You need a common interface or superclass for all your entities, that ensures they have the getLoggedId() method.
After that, something like this should work:
private void addSelect(Select<Long> select, String placeHolder, JpaRepository<? extends AbstractLogg, Long> repository) {
List<AbstractLogg> loggers = repository.findAll();
...
}
I have the following structure :
#MappedSuperclass
public abstract class Action {
...
}
And than few entities extending Action, for example :
#Entity(name="chemistry")
public class Chemistry extends Action implements Serializable {
...
}
In order to execute the persistance I used JPARepository :
#NoRepositoryBean
public interface ActionRepository<T extends Action> extends JpaRepository<T, String> {
...
}
And an interface for the children :
#Component
#Service
#Repository
interface ChemistryRepository extends ActionRepository<Chemistry> {
}
The persistance mechanism works fine for all the entities inheriting from Action.
Now I have a problem.
I have another table in the DB, called 'action_params', including additional key-value parameters which related to a specific action (i.e. chemistry).
I have the appropriate entity :
#Entity(name="action_params")
public class ActionParams implements Serializable {
...
}
In the DB I cannot use foreign key since the key is not related to a specific table but any table inherits from Action. this is ok, I would like to use a query in order to get for each Action the records from the table action_params, based on its id.
And I would like to add a List to Action, mapping those records as a list. I tried using hibernate but any query returns empty (The following code was added into Action class):
#OneToMany
#JoinFormula(value="select p from action_params p where p.action_id = id")
#Transient
protected List<ActionParams> actionParamsList;
This is not working. I also tried to autowire the ActionParamsRepository into Action, but of course there is no way doing it and I could not think of any other way.
Your advice will be much appreciated
What strong reason you have to use a JPA relation?
If there is no such reason, I'd suggest you to implement a new finder method in the ActionParams repository. Something like this:
#Query("select p from action_params p where p.action_id = :actionId")
List<ActionParams> findAllByActionId(#Param("actionId") Long actionId )
I suppose the type is Long. Replace it if you have other type.
You can use #Formula, so let me explain, when you put a query on the formula, in Hibernate is added a subquery.
#Formula("(SELECT p FROM action_params p WHERE p.action_id = id)")
private List<ActionParams> actionParamsList;
I have a problem to access service in my entity. I know, besides my code doesn't work, it's also not recomended. So, i want to know what is the best practice if i have the problem like this? Here are my class.
The Controller Class:
#Controller
#RequestMapping("step")
public class TenderController {
#Autowired
StepService stepService;
#GetMapping("")
public ModelAndView index(ModelAndView mView,
#ModelAttribute(name = "result_code") String result_code,
#ModelAttribute(name = "result_message") String result_message) {
mView.addObject("stepList", stepService.getAllSteps());
mView.setViewName("pages/step/index");
return mView;
}
}
On my view html, I iterate the stepList
<tr th:each="s:${stepList}"
th:classappend="${s?.isStepNow()?'bg-success':''}">
<!-- some td -->
</tr>
The problem is, for some reason, i have to use if else condition to get the current date to use in isStepNow() method. One from operating system. The other one from the database. So, i come up with an idea to create a service class
Here are the Service Class :
public interface TimeServices {
Date getNow();
}
and The Implementation Class:
#Service
public class TimeServicesImpl implements TimeServices {
#Value("${app.mode}")
String appMode;
#Autowired
DateDBRepository dateDBRepository;
#Override
public Date getNow() {
if(appMode.equalsIgnoreCase("GET_FROM_DB")){
Optional<DateDB> dateDBOptional = dateDBRepository.findById(1L);
if(dateDBOptional.isPresent()){
return dateDBOptional.get().getDate();
}else{
throw new IdNotExistsException();
}
}else{
return new Date();
}
}
}
The Problem is in my Entity:
#Entity
#Table(name = "step")
public class Step{
#Autowired
#Transient
TimeServices timeServices; //BAD PRACTICE AND DOESN'T WORK
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Date start;
private Date end;
public Boolean isStepNow(){
Date now = timeServices.getNow(); //THE PROBLEM
if(now.compareTo(start)>0 && end.compareTo(now)>0) {
return true;
}else{
return false;
}
}
}
Of course it doesn't work, because the timeService is always null. Anyone have some recommendation for me to solve this problem?
I know i can edit my isStepNow() to isStepNow(Date date). Then, i can access the service via controller. So i can call the isStepNow(date) on my view. But, i think it's not efficient in writing source code because i have to access the service from some controllers rather than only write it one time in the entity.
I've seen this discussion a lot.
People using DDD tend to solve it as follows:
Rename your #Entity-annotated class to StepEntity or ORMStep something similar, and only keep the fields needed to do ORM in that class.
Create a different (domain) class Step that you create using a ORMStep and dependent services, and put your domain logic methods in that class.
Let the StepService interface (better call it StepRepository) return Step class, not ORMStep.
Implement the StepRepository by injecting both the DAO (which Spring Data confusingly also calls Repository) and the dependent services, and combine them to read ORMSteps and convert to Step classes.
This seems like a lot of effort, and you probably need to convert the Step instances back to ORMStep classes too to do updates, but in the long run it's a very clean solution. You can evolve the Step classes independently of the ORM classes or switch the ORM without having to change the controller etc.
It's also TDD-friendly, since all business logic is in the domain objects, not in the ORM objects, so you can unit test them much easier.
If the classes you use have a lot of fields, MapStruct and/or Lombok Builders can keep your code cleaner.
I have an abstract base class with existing subclasses that is mostly used for defining a number of common fields and associated methods. I have a separate concrete class that "organically evolved" (i.e., bad design due to unforeseen feature requests) to end up with all the same fields defined in that abstract subclass.
Is there any way of having that separate class extend the abstract class and carry over the data of existing stored instances of that separate class? I would like to use InheritanceType.SINGLE_TABLE, but if another strategy makes it easier or possible, I guess that's fine too.
Also, those are entities referenced in other entities (OneToMany). Is that a problem? Hibernate uses only one global sequence for assigning entity ids - so it should in theory be possible to not break those references even if the data is moved to another table, right?
Already tried a few things, but no luck so far (e.g., add the "extend" to the separate class, hard-code it to use the same table as the base class, manually add a field for the discriminator...).
I am also happy about any pointers to examples/docs on how to carry out class hierarchy changes and other data model changes with JPA/Hibernate without losing data!
So, here's a simplified example of the situation. Base is the abstract base class that already has sub-classes.
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "Base")
public abstract class Base {
private long persistenceId;
private String privateField;
#Id
#GeneratedValue
public long getPersistenceId() {
return persistenceId;
}
public void setPersistenceId(long persistenceId) {
this.persistenceId = persistenceId;
}
[...]
}
#Entity
public class SubclassToBe {
private long persistenceId;
private String privateField;
private String someFieldNotInBaseClass;
#Override
#Id
#GeneratedValue
public long getPersistenceId() {
return persistenceId;
}
#Override
public void setPersistenceId(long persistenceId) {
this.persistenceId = persistenceId;
}
[...]
}
The goal would be to have SubclassToBe inherit from Base, removing the definitions of shared fields but keeping the information stored there. And at the same time, not break references to the persistence ids of SubclassToBe objects that are used in other objects as part of OneToMany relations.
I have a parent entity that has child entities (A) who in turn have their own child entities (B).
#Entity
public class Parent {
#OneToMany
Set<ChildA> childrenA;
}
#Entity
public class ChildA {
#OneToMany
Set<ChildB> childrenB;
}
I'm trying to display the data via a JSF dataTable. I would like to show the following.
Parent1 | NumberOfRelatedChildrenB
Parent2 | NumberOfRelatedChildrenB
To generate the rows in the dataTable I'm using a MangagedBean which gets a List of the Parents via a ParentFacade.findAll(), but I can't figure out how I can get a List of all the associated ChildBs. I guess I could add a #OneToMany ChildB relationship to the Parent entity, but I was hoping there would be a way to get them via the ChildA relationship?
Thanks in advance & sorry for the poor explanation!
No, I suggest to avoid creating an additional relationship in this case. One way is to create a method in the managed bean that returns the number of related ChildB given an input Parent:
#ManagedBean
public class MyManagedBean {
private List<Parent> parentList;//+getter
private Map<Long, Long> relatedChildrenB = new HashMap<Long,Long>();//+getter
#EJB
private ParentFacade parentFacade;
#PostConstruct
public void init() {
parentList = parentFacade.findAll();
for (Parent parent : parentList) {
relatedChildrenB.put(parent.getId(), parentFacade.getNumberOfRelatedChildrenB(parent));
}
}
and in the facelets page:
<h:dataTable value="#{myManagedBean.parentList}" var="parent">
...
#{myManagedBean.relatedChildrenB[parent.id]}
</h:dataTable>
and implement the corresponding queries in the facade service class.
Note that passing an object using parenthesis () in the previous revision in an EL expression requires EL 2.2 and thus either a Servlet 3.0 compatible container or applying some workaround. This solution does not need method call with parameters.
Finally, note that in my final edit I have followed the wise suggestion of skuntsel to avoid db calls in getter methods.
Simple solution would be to put a method in Parent that returns children count and then use it in dataTable column e.g.
#Transient
public int getAllChildrenCount() {
// iterate through children list and count
}
On view:
#{parent.allChildrenCount}