How do I avoid having a class and an interface per entity when using JPA with Spring Boot?
I have the following entity (and 10 other):
#Entity
#Table(name = "account")
public class Account {
#Id
#GeneratedValue
private Long id;
#Column(name = "username", nullable = false, unique = true)
private String username;
#Column(name = "password", nullable = false)
private String password;
...
In order to be able to persist this entity, I need to setup an interface per entity:
#Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
}
and then #autowire it:
#Controller
public class AdminController {
#Autowired
AccountRepository accountRepo;
Account account = new Account();
account.setUsername(...);
...
accountRepo.save(account);
If I now have 10 entities, it means that I need to define 10 #Repository interfaces and #autowire each of those 10 interfaces.
How would I embed that save method directly into Account so that I only have to call account.save() and how do I get rid of all those #repository interfaces I have to declare per entity?
Most likely not the answer you like, but I give it to you anyways.
All this interfaces per class seems useless and boilerplate when starting a project, but this changes over time when a project gets larger. There are more and more custom database interactions per entity. Additionally you can also define queries for a group of entities by subclassing the JpaRepository class.
But it is possible to improve the controller sections of your code by declaring a base class handling the repository autowire.
Example how this could be done (requires I think Spring 4)
public class BaseController<Model> {
#Autowired
protected JpaRepository<Model, Long> repository;
}
#Controller
public class AdminController extends BaseController<Admin> {
}
One option, if your entities have some kind of relationship is to use #OneToMany, #OneToOne and friends type annotations. This won't effectively replace all repository interfaces, but might reduce the amount you need. Barring that, you could try the active record pattern. Spring has Spring Roo that has some code generators for this, but I'm admittedly not a big fan of Roo. A quick Google search turned up ActiveJPA which uses the active record style and gives you the benefits you're looking for without the need for Spring Roo, just doesn't look like it's been maintained in a while. https://github.com/ActiveJpa/activejpa
Related
I am trying to replicate some functionality that I was using in Spring Data JPA in the new reactive Data r2dbc. I am aware that r2dbc is not a full-fledged ORM but wanted to understand as what could be the best way to replicate the below scenario in r2dbc:
public class Doctor extends BaseModel {
//other fields and id
#NotNull
#Enumerated(EnumType.STRING)
#ElementCollection(fetch = FetchType.LAZY, targetClass = Language.class)
#CollectionTable(name = "doctor_language",
joinColumns = #JoinColumn(name = "doctor_id"))
#Column(name = "language")
private List<Language> languages = new ArrayList<>();
#OneToMany(fetch = FetchType.LAZY, targetEntity = DoctorHealthProvider.class, mappedBy =
"doctor")
private List<DoctorHealthProvider> providers = new ArrayList<>();
// other fields
}
A simple findById call on DoctorRepository (which extends JpaRepository) will give me doctor object with list of languages from doctor_language table and list of health providers from health_provider table if I use Spring Data JPA
I was reading about projections but couldn't seem to figure out the best way to implement the above in Reactive Spring. Any help/guidelines/direction is appreciated.
Thanks
You may have a look at https://github.com/lecousin/lc-spring-data-r2dbc but looks like not yet fully ready to be used. But if your needs are not too complex it may do the job.
For example you can declare links like that:
#Table
public class TableWithForeignKey {
...
#ForeignKey(optional = false)
private LinkedTable myLink;
...
}
#Table
public class LinkedTable {
...
#ForeignTable(joinkey = "myLink")
private List<TableWithForeignKey> links;
...
}
When you want the links to be mapped, you can use either lazy loading or select with join.
If you use the methods findBy... (findById, findAll...) of the Spring repository, only the table of the repository will be loaded. In this case, you can use lazy loading. For that you need to declare a methods, with default body, and your method will be automatically implemented:
public Flux<TableWithForeignKey> lazyGetLinks() {
return null; // will be implemented
}
Another way is to make joins directly in the request. There is currently no support to automatically do joins in a repository (like #EntitiGraph in JPA), but you can implement your methods like this:
public interface MyRepository extends LcR2dbcRepository<LinkedTable, Long> {
default Flux<LinkedTable> findAllAndJoin() {
SelectQuery.from(LinkedTable.class, "root") // SELECT FROM LinkedTable AS root
.join("root", "links", "link") // JOIN root.links AS link
.execute(getLcClient()); // execute the select and map entities
}
}
The result will be all LinkedTable instances, with the list of links loaded together from the database.
Alternatively try Micronaut framework, it adopts the good parts from all existing frameworks and provides a lot of missing features in spring/SpringBoot.
For example, Micronaut Data R2dbc, it provides a collection of annotations(including one to many, many to many, embedded) to define relations like JPA, more simply you can use JPA annotations and JPA like Specifciation to customize query via type safe Criteria APIs.
Create a web application via https://micronaut.io/launch/, add R2dbc and Database drivers to dependencies, you can bring your Spring experience to Micronaut without extra effort.
My Micronaut Data R2dbc example: https://github.com/hantsy/micronaut-sandbox/tree/master/data-r2dbc
I have an Entity for example Employee that contains a #Transient Object Salary which will be derived from a related table/Entity DailyTimeRecord (DTR). DTR object data retrieval uses Joins and it is also autowired in the Employee object. The list of DTR objects will be the basis in computing the value of the Salary Object.
I found here [1]: Why is my Spring #Autowired field null? that using new keyword should be avoided, and let IoC Container create objects. In addition, I want to avoid using new keyword to minimize the coupling of my codes and ensure future compatibility and support scalability as much as possible. Therefore, I have interface Salary and implemented by a SalaryImpl class.
But Each time I tried to run the codes the autowired on a transient attribute Salary, it is always null. And I found the root cause here [2]: How to populate #Transient field in JPA? that Transient will always be null in JPA.
How will I ever create a object that avoiding the use of new keyword while it is a transient attribute?
Entity Class
#Entity
Class Employee implements Serializable {
//Attributes from DB here
#OneToMany
#JoinColumn(name="empNumber", referencedColumnName = "empNumber")
private List<DTR> dtr;
#Autowired
#Transient
private Salary salary;
//getters ang setters here
public double computeSalary(){
}
}
Interface of Salary
public interface Salary {
public double computeSalary(List<Benefit> benefits, List<Deduction> deductions);
}
Base/Implementation class of interface salary
#Service
public class SalaryImpl implements Salary, Serializable {
//other attributes here
//getter and setters
//other methods
#Override
public double computeSalary(List<Benefit> benefits, List<Deduction> deductions){
return 0;
}
}
First, #Transient is from JPA which is nothing to do with Spring .
Second, to be able to let Spring to inject beans into Employee, Employee is also required to be registered as a spring bean. But in realty, you can think that Employee is created using "new" by JPA implementation behind scene. That 's why spring cannot auto wire other beans to it.
If you really need do it, you can use AspectJ to do it as described by the docs.
I personally did not try this approach as you can simply make your SalaryService to accept an Employee as one of its argument to compute his salary, which is much simpler and easy to understand than the AspectJ approach.
public interface SalaryService {
public double computeSalary(Employee employee , List<Benefit> benefits, List<Deduction> deductions);
}
And the client code looks like:
#Service
public class EmployeeService {
#Autowired
private SalaryService salaryService;
#Transactional
public void computeEmployeeSalary(Integer employeeId){
Employee employee = entityManager.find(Employee.class , employeeId);
salaryService.computeSalary(employee, .... ,.....);
}
}
Entity objects are created by JPA implementation (like Hibernate) and not managed by spring.
They're neither Singletons nor Prototypes, so generally speaking, you cannot use Autowiring on properties of entity beans (because Autowiring is something that only can be done on spring beans).
You might be interested to read This SO thread for ideas for some workarounds.
I am trying to stream in an entire database (about 22,000 records) via Spring JPA. Using the FindAll() method I can get them in, but they are all brought into memory at once. I want to stream them.
I have tried streamAll():
#Repository
public interface GroupJsonRepository extends CrudRepository<GroupJson, String> {
Stream<GroupJson> streamAll();
}
but I get a bizarre error:
No property streamAll found for type GroupJson!
My object is:
#Entity
#Table(name = "GroupJson")
public class GroupJson {
#Id
private String id;
private String hash;
private String jsonData;
private ZonedDateTime lastUpdatedTimeStamp;
...
Is there another repository I can use that does this? I can only find CrudRepository. OR, is there some other magic JPA key words that work? I am using Spring boot 1.5.9 and I am streaming data elsewhere, but I am using a custom call:
Stream<Authority> findByPartitionKey(Long partitionKey);
You can use query if too,
#Query("select gj from Customer gj")
Stream<GroupJson> streamAll();
You have to include the "By" part in the method declaration to enable Spring Data to parse your method name. Thats the reason why you get your strange error. Spring Data interprets streamAll as a property in your entity.
#Repository
public interface GroupJsonRepository extends CrudRepository<GroupJson, String> {
Stream<GroupJson> streamAllBy();
}
I'm using spring-data-jpa with hibernate and mysql.
I have the following entity
package com.wayne.domain.player;
#Entity
#Table(name = "PLAYER")
#Getter
#Setter
#ToString
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", nullable = false)
private Long id;
#Column(name = "EMAIL")
private String email;
// ...
}
and repository:
#Repository
public interface PlayerRepository extends JpaRepository<Player, Long> {
List<Player> findByEmail(String email);
}
when I build and run the project, I'm getting the following error:
java.lang.IllegalArgumentException: No query defined for that name [Player.findByEmail]
but findByEmail method works fine.
If I modify the method with "#Query" annotation like this:
#Repository
public interface PlayerRepository extends JpaRepository<Player, Long> {
#Query("select p from Player p where p.email = ?1")
List<Player> findByEmail(String email);
}
there is no exception, and findByEmail method works fine.
I know that spring-data-jpa automatically creates queries from method names.
But why is there an exception without "#Query" annotation?
This might just be a minor bug in the Spring Data module with how the code handles the default setting for Query Lookup Strategies or perhaps you've tweaked the strategy in the #EnableJpaRepositories annotation.
Either way, the documentation says the default is CREATE_IF_NOT_FOUND which combines both CREATE and USE_DECLARED_QUERY. First it looks for a declared #NamedQuery based on the method name specified and if not found, delegates to create a new one on the fly.
I wonder (I haven't looked at the code directly) if the exception is thrown, logged, and caught as part of the logic to know to delegate to the CREATE option when USE_DECLARED_QUERY failed based on the default setting Spring data uses.
The reason introducing the #Query annotation resolves this is because that alters the strategy behavior to only use CREATE and so a named-query is generated with the value of #Query.
You might want to add a no args constructor or use the annotation for Lombok so that it generates one for you. Make sure that Lombok is working as expected, I had issues with it trying to get it to work with STS.
I have a set of JPA POJO's that contain annotations required for mapping to my domain. I also want to expose some REST services that will interact with those domain objects.
My current task is to create an android application to access these REST services. I am not able to use the domain object due to the JPA annotations they contain. The Dalvik compiler complains.
So I am looking for a strategy to be able to leverage these domain objects in a way that an Android project can also use those objects and not have to duplicate those POJO's.
Victor's suggestion to externalise the JPA mappings to XML rather than use annotations would surely work, but might be inconvenient if you're getting your JPA objects from tooling that only generates annotations.
I assume that you need on the client side Java classes that match the objects you will serialise in your REST services.
It is possible, but very tedious, to create DTO objects - POJOs exactly matching the JPA objects with suitable constructors from the JPA objects. This seems like an undue amount of effort.
It must be possible to write a source-code processor to strip the annotations from the Java. I don't think a simple regex scripting solution will work, I guess that truly parsing the source is necessary, so I hesitate to guess how much work this would be. However according to this question's answers the basic set of tools is available. I would start with this approach.
I could work out with following strategy.
This strategy works very well when you dont want fetch whole collection , or fetch with some addition criteria,
, you may retrieve it(collection relation) with named query.
use separate DAO for CRUD operation on JOIN table of many to many relation
e.g.
User can have many accounts and account can be shared by many users.
create domain models/ DAO for all the three tables,
use relation mapping for just retrieval and for DDL use individual properties.
#Entity
#Table(name="account" )
public class Account {
#Id (name="accountid")
private Long accountId;
#Column
private String type;
// removed #OneToMany as it causes issue while serializing to xml
#Transient
private Collection accountUsers;
//rest of the properties n geter setter goes here
}
#Entity
#Table(name="user")
public class User {
#Id(name="userid")
private Long userId;
#Column
private String name;
// by making transient jpa / hibernate does not initialize it with proxy.. so it remains null
/* you can populate this property using named query whenever required .*/
#Transient
private Collection userAccounts;
// rest of the properties n getter setter goes here
}
#Entity
#Table(name="AccountUser")
public class AccountUser {
// whatever your strategy to implement primary key here ,
#Id (name="accountuserid")
private Long accountUserId;
/* please note this annotation , made insertable/updatable false , relation defined just for fetching relation
*/
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "accountid", referencedColumnName = "accountid", insertable = false, updatable = false)
private Account account;
// look at insertable / updatable properties its turned off
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "userid", referencedColumnName = "userid", insertable = false, updatable = false)
private User user;
//
#Column ( name = "userid" )
private Long userId;
#Column ( name = "accountid" )
private Long accountId;
#Column ( name="opendate")
private Date opendate;
}
/* use separate dao to save above beans */
// somthing like this
public class AccountDAOImpl extends GenericDAOImpl implements AccountDAO {
}
public class UserDAOImpl extends GenericDAOImpl implements UserDAO {
}
public class AccountUserDAOImpl extends GenericDAOImpl implements AccountUserDAO {
}
I tried to explain if need any clarification kindly revert back. thanks