Is it right approach to insert Many to One entity using Hibernate? - java

I'm learning Hibernate and Querydsl on spring boot application.
I'm not sure what i'm doing is right approach or not, This is how i insert many to one entity
// BoardMaster.java
public void addBoards(Boards boards){
this.boards.add(boards);
boards.setBoardMaster(this);
}
// Service.java
#Override
#Transactional
public void save(BoardsRequestDto boardsRequestDto) {
BoardMaster boardMaster = boardMasterService.getOne(boardsRequestDto.getBbsId());
Boards boards = boardsRequestDto.toEntity();
boardMaster.addBoards(boards);
boardMasterRepository.save(boardMaster);
}
Boards is the entity has many to one relation and BoardMaster contains Boards entity as List.
So, first i try to get boardMaster and add boards into boardMaster finally run the save method.
I thought i should use or create update method to add boards into boardMaster but i found article that talks about save() can be use for update as well when i fetch entity from persistence context.
As this article says this method works fine but i'm not sure if this is fine. i feel like it's uncommon since i've never used hibernate before.
And i'm using Querydsl to update entity which feels uncommon as well
#Override
public void modify(BoardMasterRequestDto boardMasterRequestDto, Long bbsId){
queryFactory.update(boardMaster)
.set(boardMaster.bbsName, boardMasterRequestDto.getBbsName())
.set(boardMaster.bbsIntro, boardMasterRequestDto.getBbsIntro())
.set(boardMaster.isPossibleToAttachFiles, boardMasterRequestDto.isPossibleToAttachFiles())
.set(boardMaster.amountOfAttachment, boardMasterRequestDto.getAmountOfAttachment())
.set(boardMaster.availableFileSize, boardMasterRequestDto.getAvailableFileSize())
.set(boardMaster.isPossibleToReply, boardMasterRequestDto.isPossibleToReply())
.set(boardMaster.templateId, boardMasterRequestDto.getTemplateId())
.set(boardMaster.templateName, boardMasterRequestDto.getTemplateName())
.set(boardMaster.useAt, boardMasterRequestDto.isUseAt())
.set(boardMaster.bbsUseFlag, boardMasterRequestDto.getBbsUseFlag())
.set(boardMaster.targetId, boardMasterRequestDto.getTargetId())
.set(boardMaster.countOfComment, boardMasterRequestDto.getCountOfComment())
.set(boardMaster.option, boardMasterRequestDto.getOption())
.set(boardMaster.isPossibleToComment, boardMasterRequestDto.isPossibleToComment())
.set(boardMaster.satisfaction, boardMasterRequestDto.getSatisfaction())
.where(boardMaster.bbsId.eq(bbsId))
.execute();
}
This method works fine as well but i'm sure there is more efficient way to update using Hibernate. Please give me some advice 🙏🏻
If you guys need to more info, please let me know. Thank you!

Related

How to correctly implement entities that modify other entities with JPA/Hibernate?

I'm new to "normal" back-end development and I'm trying to implement comment system for my Spring Web application. The catch here is that if a comment gets downvoted then comment owner's "karma" must be decremented as well.
Now, I had some experience with web development in PHP with some self-made spaghetti-coded frameworks, where one could implement the said logic with something like that:
class Comment {
function getUser() { return db_find("users", User::class, $this->columns->owner); }
function downvote() {
$user = $this->getUser();
$user->columns->karma--;
db_persist("users", $user);
}
}
JPA beans are made differently so I couldn't reapply the above solution to it, although my initial idea was pretty similar. I believe it is possible to achieve something like that by passing Session to the downvote method this way:
class Comment {
// ...
#ManyToOne
#JoinColumn(name = "owner_id")
var owner: User? = null
fun downvote(session: Session) {
this.rating -= 1;
this.owner.karma -= 1;
session.save(this.owner)
session.save(this)
}
}
But it seems really wrong and unnatural to me. I've also had an idea to put this logic in controller, but that one seems like a bad practice too...
So, I kinda have an entity (comment) that should modify another entity (user) on modification (call to downvote) and I'm not sure how to implement all this in JPA-like way.
First of all, you should separate the logic (e.g., transaction operations) from the entity.
The entity should be (more or less) a pojo, you create another class for those business logic implementations.
A typical architecture would be:
entities: pojos
dao or repositories: each repository usually talks (read/write) to one entity class
services (here you can modify as many entities as you need through required repositories)

How can I insert data into the database when an entity is created?

I'm creating a website for a school project which uses spring for the backend. I'm trying to insert data into the database when new data is saved to a specific table.
I've tried using #HandleAfterCreate and #PrePersist, but neither worked. I'm not very experienced with spring. The teacher told us to use it and now I don't know what do.
#HandleAfterCreate
public void handlePersonBeforeCreate(Person person){
logger.info("Inside Person Before Create....");
Set<Qualifikation> qualifikationen = new HashSet<>();
kompetenzRepository.findAll().forEach(kompetenz -> {
Qualifikation qualifikation = new Qualifikation();
qualifikation.setAusmass(0);
qualifikation.setKompetenz(kompetenz);
qualifikation.setPerson(person);
});
person.setQualifikationen(qualifikationen);
System.out.println(person.getDisplayName());
}
The code should set a person's "Qualifikation" to a default value when the person is inserted (via OAuth login). It should have every "Kompetenz" with a value of 0 by default. Kompetenz has a 1 to n relation to Qualifikation. If you need more information please ask me.
It looks like you're trying to have access to the repository layer of your application inside an entity. This is generally not a good idea, as the entities should only know about the data they hold, not the other application components.
In this particular case it would be wise to use a #Service class with a method that you can call to insert the data into the database. In the method you could then insert any other entities as well. Let your repositories be fields of the service and make them #Autowired.
I think you need to enable JPA auditing . It can be enabled in Spring by add #EnableJpaAuditing to your persistence configuration. This tells Spring to listen JPA entity lifecycle events and call the annotated methods in appropriate places.
Also I think you should make the callback method private if it is meant to be called only when persisted (#PrePersist).
See details here. In this article is also presented entity listeners which might also be a good solution when dealing with multiple entities having a need for same pre-persist functionality.
I think you should create a service class, a repository class and an entity which will be stored through repository. The logic of getting all inner elements and filling it with default value is to be written in service and not a good idea to write in entity class.
If you need any help regarding it, let me know .
Welcome to community!!

Use Spring Data JPA, QueryDSL to update a bunch of records

I'm refactoring a code base to get rid of SQL statements and primitive access and modernize with Spring Data JPA (backed by hibernate). I do use QueryDSL in the project for other uses.
I have a scenario where the user can "mass update" a ton of records, and select some values that they want to update. In the old way, the code manually built the update statement with an IN statement for the where for the PK (which items to update), and also manually built the SET clauses (where the options in SET clauses can vary depending on what the user wants to update).
In looking at QueryDSL documentation, it shows that it supports what I want to do. http://www.querydsl.com/static/querydsl/4.1.2/reference/html_single/#d0e399
I tried looking for a way to do this with Spring Data JPA, and haven't had any luck. Is there a repostitory interface I'm missing, or another library that is required....or would I need to autowire a queryFactory into a custom repository implementation and very literally implement the code in the QueryDSL example?
You can either write a custom method or use #Query annotation.
For custom method;
public interface RecordRepository extends RecordRepositoryCustom,
CrudRepository<Record, Long>
{
}
public interface RecordRepositoryCustom {
// Custom method
void massUpdateRecords(long... ids);
}
public class RecordRepositoryImpl implements RecordRepositoryCustom {
#Override
public void massUpdateRecords(long... ids) {
//implement using em or querydsl
}
}
For #Query annotation;
public interface RecordRepository extends CrudRepository<Record, Long>
{
#Query("update records set someColumn=someValue where id in :ids")
void massUpdateRecords(#Param("ids") long... ids);
}
There is also #NamedQuery option if you want your model class to be reusable with custom methods;
#Entity
#NamedQuery(name = "Record.massUpdateRecords", query = "update records set someColumn=someValue where id in :ids")
#Table(name = "records")
public class Record {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//rest of the entity...
}
public interface RecordRepository extends CrudRepository<Record, Long>
{
//this will use the namedquery
void massUpdateRecords(#Param("ids") long... ids);
}
Check repositories.custom-implementations, jpa.query-methods.at-query and jpa.query-methods.named-queries at spring data reference document for more info.
This question is quite interesting for me because I was solving this very problem in my current project with the same technology stack mentioned in your question. Particularly we were interested in the second part of your question:
where the options in SET clauses can vary depending on what the user
wants to update
I do understand this is the answer you probably do not want to get but we did not find anything out there :( Spring data is quite cumbersome for update operations especially when it comes to their flexibility.
After I saw your question I tried to look up something new for spring and QueryDSL integration (you know, maybe something was released during past months) but nothing was released.
The only thing that brought me quite close is .flush in entity manager meaning you could follow the following scenario:
Get ids of entities you want to update
Retrieve all entities by these ids (first actual query to db)
Modify them in any way you want
Call entityManager.flush resulting N separate updates to database.
This approach results N+1 actual queries to database where N = number of ids needed to be updated. Moreover you are moving the data back and forth which is actually not good too.
I would advise to
autowire a queryFactory into a custom repository
implementation
Also, have a look into spring data and querydsl example. However you will find only lookup examples.
Hope my pessimistic answer helps :)

How to extend / edit Netbeans' auto-generated REST services?

I am new to the automatic generation of REST services code from a Database in Netbeans 8.
Disclaimer: after a discussion (in the comments), I realized that I
should warn to avoid this question if you are not familiar with the
automatic generation of REST service from DB in Netbeans
(https://netbeans.org/kb/docs/websvc/rest.html). That's because you
need to know what is going on and where to put your hands in order to
edit them. I don't provide any non-working code here, rather I want to know what should I do in order to edit such services. I provide an example of what I would like to obtain.
I did the automatic code generation of REST services from DB and obtained entity classes and service "facade" classes. What I need to do now is to extend / edit those services and I don't know where to put my hands.
For instance, consider the following scenario. I have a student and he/she passed many exams. From the DB perspective, student-exam is a 1 to many relationship.
When I test the rest API and perform a GET by ID of a student, the resulting JSON does not contain a collection of exams associated with that student, as expected.
Where and how should I change the auto-generated service code in order to obtain the exams collection within the student's json?
In other words, assuming I perform a GET to
../student/12, what I want to obtain is:
{
"id":12, "name":"Marco", "age":26, "exams": [
{ "id":1, "exam_name":"Computer Networks" },
{ "id":15, "exam_name":"Algorithms"}
]
}
Best regards
So it seems like I got it working, but I don't know if this is the most elegant way to do it.
Here's what I did.
1) go to the auto-generated child entity class (i.e. Exam.java) and add a custom NamedQuery to the NamedQueries annotation. For instance,
#NamedQueries({
... default auto-generated stuff ...
#NamedQuery(name = "getExamsOfStudent", query="SELECT e FROM Exams e WHERE e.student.ID =: studentID")})
2) go to the auto-generated child REST service class (i.e. ExamFacadeREST.java and add a method to retrieve the children of a parent, using the NamedQuery defined in the entity class. For instance,
#GET
#Path("studentID/{studentID}")
#Produces({"application/xml","application/json"})
public List<Exam> getExamsOfStudent(#PathParam("studentID") Integer studentID) {
javax.persistence.Query query = getEntityManager().createNamedQuery("Exam.getExamsOfStudent");
query.setParameter("studentID", studentID);
return query.getResultList();
}
At this point you have a REST service that retrieves the children of a parent entity.
However, this is not what I originally asked for. I would like to retrieve the exams as a collection, inside the student JSON, when performing the GET (by id) of the student.
In order to do so simply go to the father entity REST service, (i.e. StudentFacadeREST.java) and properly edit the find method.
Does anyone know a more elegant way to do it?
EDIT 1: I'm trying the second method (to retrieve the exams as a collection, inside the student JSON, when performing the GET of the student) and I see that the collection does not get serialized into the output JSON. Any advice on this?
EDIT 2: I got it. For "generally extending" a service the first part of my answer above is good. However, if you want to obtain the serialization of collections that are already mapped as relations in the DB, simply properly remove the relative #XmlTransient annotations in the entity auto-generated classes. Take care to avoid circular references, that is you will probably need to add an #XmlTransient annotation to the "getParent()" method in the child entity class.
For this example:
(A) go to the Student entity class and remove the #XmlTransient annotation from the getExamsCollection() method;
(B) go to the Exam entity class and add the #XmlTransient annotation to the getStudent() method.
Hope this helps.
Best regards

JPA managed entities vs JavaFX properties

My current project is done using JavaFX. I use properties to bind (bidirectionnal) view fields to bean (with BeanPathAdapter of JFXtras).
I choose to use JPA with ObjectDB as model.
This is the first time I use JPA in a standalone project and here I'm facing the problem of managed entities.
Actually, I bind managed entities to view fields and when the value of a view field changes, the entities is updated... and the database also.
I'm trying to find a way to manually persist/merge an entity so I can ask the user if he wants to save or not.
Here's the code i use to get list :
EntityManagerFactory emf = Persistence.createEntityManagerFactory("$objectdb/data/db.odb");
EntityManager em = emf.createEntityManager();
List<XXX> entities = em.createQuery("SELECT x FROM XXX x").getResultList();
So when i do
entity.setName("test");
the entity is updated in the database.
What i'm looking for is that the entity doesn't update automatically.
I tried (just after the getResultList)
em.clear();
or
em.detach(entity);
but it looses the relations instances even with CascadeType.DETACH.
I also tried
em.setFlushMode(FlushModeType.COMMIT);
but it still updates automatically...
I also tried to clone the object. But when i want to merge it, it gives me an exception :
Attempt to reuse an existing primary key value
I thought an alternative solution : use a variable as 'buffer' and fill the managed bean with buffer if the user saves. But BeanPathAdapter looses its sense. It's the same as filling view fields manually and filling bean fields manually after saving.
Could you help me to find a solution ?
Thanks,
Smoky
EDIT:
I answer to my own question :p
After 3 hours of research, I found a solution.
The 'cloning' solution was the 'best' of each I quoted but I don't think it's the best one.
The cause of the exception was the code I used to persist/merge my entity. I was persisting an entity non-managed with an already existing id. I thought I was merging...
I did a generic method not to fail again
public <T extends IEntity> T persist(T object) {
em.getTransaction().begin();
if (object.getId() == null) {
em.persist(object);
em.flush();
em.getTransaction().commit();
em.refresh(object);
}
else {
object = em.merge(object);
em.getTransaction().commit();
}
return object;
}
So the solution : When I have to bind the entity to the view, I use entity.clone() so I can use the entity as non-managed and merge when I want.
But if you have a proper solution, i'm interested :)
Thanks again
In addition to the solution above, standard solutions are:
Use detached objects in the model and then merge them into the EntityManager.
Use managed objects in the model, keeping the EntityManager open (with no detach/merge).

Categories

Resources