Difference between obtaining object via getter vs via repository - java

Suppose I have these 2 entity classes:
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
List<Order> orderList;
/* getters and setters */
}
#Entity
public class Order {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#ManyToOne
private User user;
/* getters and setters */
}
And this repository:
public interface OrderRepository extends CrudRepository<Order, Long> {
List<Order> findByUser(User user);
}
Now if I have user which is a managed entity and want to get list of orders that belong to that user I can do that in 2 ways:
Method 1:
#Autowired
OrderRepository orderRepository;
List<Order> orderList = orderRepository.findByUser(user);
Method 2:
List<Order> orderList = user.getOrderList();
Is there any difference between these 2 methods? When should I use method 1 and when should I use method 2?
Thanks

Depending on the exact configuration of fetch strategy and lazy loading, there might be a difference in when and how the data is actually loaded from the database. But this is not what should drive your decision, which access-path you are using.
A very central idea behind Spring Data is that of the Aggregate as described in Domain Driven Design. There should be one Repository per Aggregate. So the question becomes: Are User and Order part of the same Aggregate? If one assumes that you adhered to the idea behind Spring Data, the fact that there are repositories for User and Order means, they are both Aggregate Roots and therefore can't be part of the same Aggregate. Also, therefore I would not use the reference and actually remove it (and I most certainly would remove the Cascade.All
But you obviously didn't make the design decision based on that argument, so you might as well decide that both shall be part of the same Aggregate and drop one of the repositories.

Related

Dynamic fetching for relations in Spring JPA

I want to be able to dynamically load the relations of my entity, depending on which RestService got called.
Entity classes:
#Entity
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
private Buyer buyer;
// some more attributes
}
#Entity
public class Buyer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
// some more attributes
}
RestController class:
#GetMapping
public Iterable<Order> getAll() {
// here I want JPA to NOT load the buyers for the order
return orderRepository.findAll();
}
#GetMapping("/{id}")
public Order get(#PathVariable("id") String id) {
// here I want JPA to load the buyers for the order
return orderRepository.findById(Long.parseLong(id)).orElseThrow();
}
None of the two fetchtypes LAZY and EAGER or json annotations (like #JsonIgnore, #JsonIdentityInfo, #JsonManagedReference and #JsonBackReference) seem to make this possible as far as I understood and tried.
If this is not possible, maybe someone can explain how to solve this problem then. On the one hand I sometimes need those relations in my frontend to display some values and on the other hand when I always load them I get huge performance problems or infinity recursions.
I don't think JPA supports your use case directly.
One option is to create the same entity twice - one with eager and the other with lazy. Switch them in the methods.
Another option is to use a DTO (Data Transfer Object) as the response, instead of the entity class itself. You will have to write a mapper logic to convert an entity to DTO though.

Designing hierarchy of parent child relationships with spring data jpa

I am trying to build a to-do log keeper.
I am using java spring-boot with data-jpa which is built on hibernate.
I want a user to have several projects the user works on. Every project then has several tasks associated with it and the user tracks how much time was spent per a task by completing short atomic units of work (log entries).
So far I ended up building the most naive implementation of this system. It looked like several levels of one to many hierarchy: user->projects->tasks->entries. The current db implementation is based on a schema like this
Code for entity classes (getters setters constructors and some annotations are omitted for brevity):
#MappedSuperclass
public abstract class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
}
#Entity
public class User extends AbstractEntity {
#Column
private String name;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Project> projects;
}
#Entity
public class Project extends AbstractEntity {
#Column
private String name;
#OneToMany(mappedBy = "project", fetch = FetchType.LAZY)
private List<Task> tasks;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
}
#Entity
public class Task extends AbstractEntity {
#Column
private String name;
#OneToMany(mappedBy = "task", fetch = FetchType.LAZY)
private List<Entry> entries;
#ManyToOne
#JoinColumn(name = "project_id")
private Project project;
}
#Entity
public class Entry extends AbstractEntity {
#Column
private Integer duration;
#Column
private LocalDateTime finish;
#ManyToOne
#JoinColumn(name = "task_id")
private Task task;
}
I want to be able to provide functionality for a user to view all the log entries in a user specified time frame. I added jpa repository like this:
public interface EntryRepository extends JpaRepository<Entry, Integer> {
#Query("SELECT e FROM Entry e WHERE (e.task.project.user.id=:user_id) AND " +
"(e.finish BETWEEN :from AND :to)")
List<Entry> getAllForUserInDateRange(#Param("from") LocalDateTime from,
#Param("to") LocalDateTime to,
#Param("user_id") int userId);
}
1) Is it correct to say that this query is inefficient? I was thinking performing a fetch like this from a database is inefficient because the query cannot take advantage of indexes. Since there is no foreign key user_id in the Entry table every row is being looked up and the chain entry->task->project->user is being followed. I end up with linear complexity instead of logarithmic.
2) What is a better way to solve the problem? Is it ok to store the foreign key to the user in the Entry table? If I will want to fetch entries from the database for a particular project or a task, then I will have to add foreign keys to these relationships as well. Is that ok?
You should check real SQL which is being executed. Set org.hibernate.SQL log level to DEBUG and you'll see the statements.
I think for your query you will actuall get three inner joins between four tables. You say the query cannot take advantage of indexes. It absolutely can. Create following indexes:
USER (ID)
PROJECT (USED_ID, ID)
TASK (PROJECT_ID, ID)
ENTRY(TASK_ID, ID)
See Contactenated Indexes from Use the Index, Luke.
With these indexes your joins across four tables will likely use indexes. I won't put my hand in fire for this, but it should work. Check the query plan.
You are right that the chain ENTRY->TASK->PROJECT->USER will be followed, but it should be quite faset with indixes
Your database schema is pretty normalized, which results in three joins across four tables. You could denormalize this schema by bringing, say, user_id to the ENTRY. This may improve query performance, but honestly I doubt this will bring much. You may want to run real-world benchmark before actually switching to this solution.

Spring Data delete function not deleting records

I have the following simple application
Users Entity
#Entity
public class Users implements Serializable {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
private Set<UserRoleUser> userRoleUser;
// GETTERS AND SETTERS
}
UserRole Entity
#Entity
public class UserRole implements Serializable {
#Id
#GeneratedValue
private long id;
private String roleName;
#OneToMany(mappedBy = "userrole", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<UserRoleUser> userRoleUser;
// GETTERS AND SETTERS
}
UserRoleUser Many to many resolver class
#Entity
public class UserRoleUser implements Serializable {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn(name = "fk_userId")
private Users user;
#ManyToOne
#JoinColumn(name = "fk_userroleId")
private UserRole userrole;
// GETTERS AND SETTERS
}
UserRoleUserRepository
#Repository
#Transactional
public interface UserRoleUserRepository extends JpaRepository<UserRoleUser, Long>, QueryDslPredicateExecutor<UserRoleUser>{
}
Main Application class
#SpringBootApplication
#Configuration
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
UserRoleUserRepository userRoleUserRepository = context.getBean(UserRoleUserRepository.class);
Iterable<UserRoleUser> findAll = userRoleUserRepository.findAll(QUserRoleUser.userRoleUser.id.gt(0));
for (UserRoleUser userRoleUser : findAll) {
userRoleUserRepository.delete(userRoleUser);
}
}
}
On running the main application, the database records in the UserRoleUser table are not being deleted. What could be the issue? I am using Spring Data and QueryDsl.
I have also tried putting the delete functionality on a Controller but still doesn't work.
#RestController
#RequestMapping("/api")
public class DeleteController {
#Autowired
UserRoleUserRepository userRoleUserRepository;
#GetMapping("/delete")
public String delete() {
Iterable<UserRoleUser> findAll = userRoleUserRepository.findAll(QUserRoleUser.userRoleUser.id.gt(0));
for (UserRoleUser userRoleUser : findAll) {
userRoleUserRepository.delete(userRoleUser);
}
return new Date().toString();
}
}
If you need to use the given methods provided by CrudRepository, use the JpaRepository.deleteInBatch(). This solves the problem.
The problem is the entities are still attached and will not be deleted until they become detached. If you delete by their id instead of the entity itself, it will delete them.
One thing I noticed is you are deleting the users one at a time which could lead to a database performance hit as the query will be recreated each time. The easiest thing to do is to add all the ids to a set then delete the set of ids. Something like this:
Set<Integer> idList = new HashSet<>();
for (UserRoleUser userRoleUser : findAll) {
idList.add(userRoleUser.getId());
}
if (!idList.isEmpty()) {
userRoleUserRepository.delete(idList);
}
then in your repository add the delete method
#Modifying
#Query("DELETE FROM UserRoleUser uru WHERE uru.id in ?1")
#Transactional
void delete(Set<Integer> id);
The reason why the child objects (UserRoleUser) are not being deleted upon userRoleUserRepository.delete(userRoleUser) call is that each UserRoleUser points to a Users which in turn holds a #OneToMany reference Set<UserRoleUser> userRoleUser.
As described in this StackOverflow answer, what your JPA implementation (e.g. Hibernate) effectively does is:
The cache takes note of the requested child exclusion
The cache however does not verify any changes in Set<UserRoleUser>
As the parent #OneToMany field has not been updated, no changes are made
A solution would go through first removing the child element from Set<UserRoleUser> and then proceed to userRoleUserRepository.delete(userRoleUser) or userRepository.save(user)
In order to avoid this complication two answers have been provided:
Remove element by Id, by calling userRoleUserRepository.deleteById(userRoleUser.getId()) : in this case the entity structure (and therefore the parent) is not checked before deletion. In the analog case of deleteAll something more convoluted such as userRoleUserRepository.deleteByIdIn(userRoleUserList.stream().map(UserRoleUser::getId).collect(Collectors.toList())) would have to be employed
Convert your CrudRepository to a JpaRepository and use its deleteInBatch(userRoleUserList) method. As explained in this article and this StackOverflow answer the deleteInBatch method tries to delete all records at once, possibly generating a StackOverflow error in the case the number of records is too large. As repo.deleteAll() removes one record at a time this error it minimizes this risk (unless the call is itself inside a #Transactional method)
According to this StackOverflow answer, extra care should be used when recurring to deleteInBatch as it:
Does not cascade to other entities
Does not update the persistence context, requiring it to be cleared (the method bypasses the cache)
Finally , as far as I know , there is no way this could be done by simply calling userRoleUserRepository.delete(userRoleUser) without first updating the parent object. Any updates on this (whether by allowing such behaviour through annotations, configuration or any other means) would be a welcome addition to the answer.

Orchestrating Spring Boot CrudRepositories with foreign key relationships

I am writing a Spring Boot application that will use Hibernate/JPA to persist between the app and a MySQL DB.
Here we have the following JPA entities:
#MappedSuperclass
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private Long id;
#Type(type="uuid-binary")
private UUID refId;
}
#Entity(name = "contacts")
#AttributeOverrides({
#AttributeOverride(name = "id", column=#Column(name="contact_id")),
#AttributeOverride(name = "refId", column=#Column(name="contact_ref_id"))
})
public class Contact extends BaseEntity {
#Column(name = "contact_given_name")
private String givenName;
#Column(name = "contact_surname")
private String surname;
#Column(name = "contact_phone_number")
private String phone;
}
#Entity(name = "assets")
#AttributeOverrides({
#AttributeOverride(name = "id", column=#Column(name="asset_id")),
#AttributeOverride(name = "refId", column=#Column(name="asset_ref_id"))
})
public class Asset extends BaseEntity {
#Column(name = "asset_location")
private String location;
}
#Entity(name = "accounts")
#AttributeOverrides({
#AttributeOverride(name = "id", column=#Column(name="account_id")),
#AttributeOverride(name = "refId", column=#Column(name="account_ref_id"))
})
public class Account extends BaseEntity {
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "contact_id", referencedColumnName = "contact_id")
private Contact contact;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "asset_id", referencedColumnName = "asset_id")
private Asset asset;
#Column(name = "account_code")
private String code;
}
And the #RestController, where an Account instance will be POSTed (to be created):
public interface AccountRepository extends CrudRepository<Account, Long> {
#Query("FROM accounts where account_code = :accountCode")
public Account findByCode(#Param("accountCode") String accountCode);
}
#RestController
#RequestMapping(value = "/accounts")
public class AccountController {
#Autowired
private AccountRepository accountRepository;
#RequestMapping(method = RequestMethod.POST)
public void createNewAccount(#RequestBody Account account) {
// Do some stuff maybe
accountRepository.save(account);
}
}
So the idea here is that "Account JSON" will be sent to this controller where it will be deserialized into an Account instance and (somehow) persisted to the backing MySQL. My concern is this: Account is a composition (via foreign keys) of several other entities. Do I need to:
Either create CrudRepository impls for each of these entities, and then orchestrate save(...) calls to those repositories such that the "inner-entitities" get saved first before the "outer" Account entity?; or
Do I just save the Account entity (via AccountRepository.save(account)) and Hibernate/JPA automagically takes care of creating all the inner/dependendent entities for me?
What would the code/solution look like in either scenario? And how do we specify values for BaseEntity#id when it is an auto-incrementing PK in the DB?
That depends on your design and specific use cases, and what level of flexibility you want to keep. Both ways are used in practice.
In most CRUD situations, you would rather save the account and let Hibernate save the entire graph (the second option). Here you usually have another case which you didn't mention, and it is updating of the graph, which you would probably do the same way, and actually the Spring's repository save method does it: if the entity is a new (transient) one, it persists it, otherwise it merges it.
All you need to do is to tell Hibernate to cascade the desired entity lifecycle operations from the Account to the related entities:
#Entity
...
public class Account extends ... {
#OneToOne(..., cascade = {CascadeType.PERSIST, CascadeType.MERGE})
...
private Contact contact;
#OneToOne(..., cascade = {CascadeType.PERSIST, CascadeType.MERGE})
...
private Asset asset;
...
}
However, you pay the penalty of reloading the object graph from the db in case of merge operation, but if you want everything done automatically, Hibernate has no other way to check what has actually changed, other than comparing it with the current state in the db.
Cascade operations are applied always, so if you want more flexibility, you obviously have to take care of things manually. In that case, you would omit cascade options (which is your current code), and save and update the parts of the object graph manually in the order that does not break any integrity constraints.
While involving some boilerplate code, manual approach gives you flexibility in more complex or performance-demanding situations, like when you don't want to load or reinitialize the parts of the detached graph for which you know that they are not changed in some context in which you save it.
For example, let's assume a case where there are separate web service methods for updating account, contact and asset. In the case of the account method, with cascading options you would need to load the entire account graph just to merge the changes on the account itself, although contact and asset are not changed (or worse, depending on how you do it, you may here revert changes on them made by somebody else in their dedicated methods in the meantime if you just use the detached instances contained in the account).
Regarding auto-generated ids, you don't have to specify them yourself, just take them from the saved entities (Hibernate will set it there). It is important to take the result of the repository's save method if you plan to use the updated entity afterwards, because merge operation always returns the merged copy of the passed-in instance, and if there are any newly persisted associated entity instances in the updated detached graph, their ids will be set in the copy, and the original instances are not modified.

Data JPA - Remove entities from repository

I have an existing java application in which I use now spring-boot and data jpa to save some data in a database. In one class Order which I convert now to an #Entity I have a member which is a List<Position>. Following is the code of the reduced classes
#Entity
public class Order
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private List<Position> positions;
//some other members follow here...
}
#Entity
public class Position
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
//some members follow here...
}
So what I have done is the following, I added the annotation #Transient to my list in Order and add inPosition a reference to an Order:
#Entity
public class Order
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Transient
private List<Position> positions;
//some other members follow here...
}
#Entity
public class Position
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#ManyToOne
private Order order;
//some members follow here...
}
Now when I want to save an Order object, then I save first the Order in the corresponding repository and then go through the list of Positions and set in ervery the reference to Order and then save the Position object to its corresponding repository. If I want to fetch an Order then I fetch first the Order and then fetch the Positions in the correspoding repository with findByOrder(..).
So far this works. Now I'm facing the problem, if the application modifies in the Order the list with the Positions and I have to update the database with the new Order object, then I find no smooth solution to delete the Positions in the repository which were removed from the list by the application (as I have no longer a reference to the removed ones). I could delete first all Positions of that Order and then save the existing ones again.
So my questions is maybe if there is a better way to remove the Positions in the repository which were removed by the application. But maybe it would be an XY question, cause my approach how to save the Position-List is the reason why I am facing this problem. I appreciate any hints concering this.
You're not doing it right.
First, it's not clear why you're making the #OneToMany side #Transient.
Best is to use cascade features of JPA.
In your example, if you put:
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval=true)
private List<Position> positions;
All operations on Order will cascade on Positions aswell, so you don't need to explicitly manage them.
See these examples with Hibernate

Categories

Resources