We have an application with the following set up:
Java 6.0
Spring Data JPA 1.1.0.RELEASE
Spring Data MongoDB 1.0.2.RELEASE
Spring Data MongoDB Cross-Store 1.0.2.RELEASE
Hibernate JPA 2.0
We have several classes in this application that use the JPA PrePersist, PreUpdate, PostPersist and PostUpdate annotations. An example is given below.
#Entity
public class Person
{
private String password;
#PrePersist
#PreUpdate
public void beforeSave()
{
if(!Security.isEncrypted(this.password))
{
this.password = Security.encrypt(this.password);
}
}
}
As soon as we turn on AspectJ weaving for the cross-store plugin, the Spring application context fails to load with the error:
Caused by: javax.persistence.PersistenceException: You can only annotate one callback method with javax.persistence.PrePersist in bean class: org.example.domain.Person
at org.hibernate.ejb.event.CallbackResolver.resolveCallback(CallbackResolver.java:110)
at org.hibernate.ejb.event.EntityCallbackHandler.addCallback(EntityCallbackHandler.java:123)
at org.hibernate.ejb.event.EntityCallbackHandler.add(EntityCallbackHandler.java:61)
at org.hibernate.ejb.event.JpaIntegrator.integrate(JpaIntegrator.java:151)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:306)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1744)
at org.hibernate.ejb.EntityManagerFactoryImpl.<init>(EntityManagerFactoryImpl.java:94)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:905)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:890)
at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:74)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:268)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:310)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452)
I have found out that the root cause for the error is that the Aspect MongoDocumentBacking weaves additional PrePersist and PreUpdate methods into entity classes. Since the classes already have methods with these annotations, Hibernate Entity Manager fails to validate these classes.
Is there any guidance on how the cross-store plugin should be used with applications that have existing code that use JPA annotations?
I was facing the same issue with #PreUpdate and #PostLoad.
There is a bug opened in springsource about this:
https://jira.springsource.org/browse/DATAMONGO-519
They have given the solution below:
Create JPA entity event listener classes and move PrePersist, PreUpdate, etc. code into these listeners.
Change the aspects to first search whether an entity class has any field annotated as RelatedDocument.
If an entity class has one or more fields annotated as RelatedDocument, check whether the class already has EventListeners annotation.
If the EventListeners annotation is already present, add cross-store event listeners to the list. If not, add the EventListeners annotation to the class.
I moved all my annotations to an entity listener and it worked straight away, no need to change anything else.
Have a look at the following link, it seems that only one annotation of the same time can be added at the entity level, but many can be added using entity listeners: http://docs.jboss.org/hibernate/stable/entitymanager/reference/en/html/listeners.html
#Entity
#EntityListeners(ProductEntityListener.class)
public class Product {
}
public class ProductEntityListener {
#PrePersist
#PreUpdate
protected void prePersist(Product entity) {
}
#PostLoad
protected void postLoad(Product entity){
}
}
We had the same issue here:
an EntityListener implemented an Interface with a generic parameter. After debugging the Hibernate code that threw the exception it turned out that it is not possible to use Generics in this listener interface for the entity type. After removing the generic parameter it worked.
Reason: When using a generic parameter in the interface the java compiler creates (for example) one postUpdate(...)-method for the implementing class (concrete type) and one for the interface (super type). Hibernate detects those 2, but expects only one. As a consequence it throws the exception.
Related
I use Spring Boot Data REST, yes, I write something like below:
#RepositoryRestResource
public interface ExerciseRepository extends JpaRepository<Exercise, Integer> {}
then I open 127.0.0.1/exercises. It will show all exercises.
But I want only show some appointed exercises(eg. exercise id < 100, or other complicated logic) on the 127.0.0.1/exercises.
I know I can use #RestController, but how can I do this with Spring Boot Data REST?
#RepositoryRestResource(path="exercises",collectionResourceRel = "exercises")
can you edit this according to your own code ? I think this will work for you
You can declare an interface method, for example:
#RepositoryRestResource
public interface ExerciseRepository extends JpaRepository<Exercise, Integer> {
List<Exercise> findByIdLessThan(#Param("id") Integer id);
}
In this case, the query is derived from the method name directly, but you can also write a query manually using #Query, for more details check the documentation.
To invoke the method use the following API request:
GET http://localhost:8080/exercises/search/findByIdLessThan?id=100
For reference, Spring Data REST - Useful notes.
EDIT:
If you use Hibernate as your persistence provider, you can use #Where for static filtering, and #Filter for dynamic filtering where filters are defined and configured at runtime, according to Hibernate User Guide.
For example, you can annotate the entity with #Where and define a condition that will be applied to all queries related to that entity:
#Where(clause = "id<100")
#Entity
public class Exercise{
//...
}
I have just come upon something that I can't describe in any other way than bizarre.
I have a service that is supposed to do this:
it gets passed an external identifier of a customer
it looks up the customer's internal ID
then loads and returns the customer
I'm using optionals as there is a potential chance that external identifiers can't be resolved.
#Transactional(readOnly = true)
public Optional<Customer> getCustomerByExternalReference(String externalId, ReferenceContext referenceContext) {
return externalIdMappingService.resolve(externalId, referenceContext, InternalEntityType.CUSTOMER)
.map(x->new CustomerId(x.getTarget()))
.map(customerRepository::getById);
}
what's noteworthy is here is that: externalIdMappingRepository.resolve returns an Optional<ExternalIdReference> object. If that is present, I attempt to map it to a customer that I then look up from the database. customerRepository is a regular spring data JPA repository (source code below)
However, when trying to access properties from Customer outside the service, I get an exception like this:
org.hibernate.LazyInitializationException: could not initialize proxy [Customer#Customer$CustomerId#3e] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at Customer$HibernateProxy$R0X59vMR.getIdName(Unknown Source)
at CustomerApiModel.<init>(CustomerApiModel.java:27)
I understand that this means, that Hibernate decided to lazy load that entity. Once outside the transactional boundaries of the service, it's not able to load the data for that object anymore.
My Question is: Why does Hibernate/Spring Data try a lazy fetching strategy when I essentially just load a specific object by ID from a Spring Data Repository and how I can disable this behaviour the right way.
I'm aware that there is a couple of workarounds to fix the problem (such as allowing hibernate to open sessions at will, or to access properties of that object inside the service). I'm not after such fixes. I want to understand the issue and want to ensure that lazy fetching only happens when it's supposed to happen
Here's the code for customer (just the part that I think is helpful)
#Entity
#Table(name="customer")
#Getter
public class Customer {
#EmbeddedId
private CustomerId id;
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public static class CustomerId implements Serializable {
private long id;
public long asLong() {
return id;
}
}
}
and here's the source code of the repository:
public interface CustomerRepository extends Repository<Customer, CustomerId> {
List<Customer> findAll();
Customer getById(CustomerId id);
Optional<Customer> findOneById(CustomerId id);
Optional<Customer> findOneByIdName(String idName);
}
By declaring the method Customer getById(CustomerId id); in your CustomerRepository interface, you chose to let your repostory selectively expose the corresponding method with the same signature from the standard spring-data repository methods, as explained by the Repository java doc:
Domain repositories extending this interface can selectively expose CRUD methods by simply declaring methods of the same signature as those declared in CrudRepository.
Different to what the doc says, this also includes methods from JpaRepository.
In the case of Customer getById(CustomerId id);, you therefore invoke the JpaRepository method with the same signature: T getOne(ID id);, which only invokes EntityManager#getReference , as suggested by it's doc:
[...] Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is implemented this is very likely to always return an instance and throw an {#link javax.persistence.EntityNotFoundException} on first access. Some of them will reject invalid identifiers immediately. [...]
#see EntityManager#getReference(Class, Object) for details on when an exception is thrown.
When calling EntityManager#getReference, Hibernate first returns a non-initialized proxy of the Entity without executing any SQL statement at all, which is why your method only returns the non-initialized entity.
To fix this, you could change your service logic as follows:
#Transactional(readOnly = true)
public Optional<Customer> getCustomerByExternalReference(String externalId, ReferenceContext referenceContext) {
return externalIdMappingService.resolve(externalId, referenceContext, InternalEntityType.CUSTOMER)
.map(x->new CustomerId(x.getTarget()))
.map(id -> customerRepository.findOneById(id).get()); // <-- changed call
}
This way, spring-data would invoke CrudRepository#findById, which would internally call EntityManager#find and therefore return an initialized entity (or an empty Optional if none was found in the DB).
Related:
When use getOne and findOne methods Spring Data JPA
Why "findById()" returns proxy after calling getOne() on same entity? (attention when using getOne and findById in the same transaction)
I am not so into Spring Data JPA and I have the following problem working on a Spring Boot project.
I have the following architectural doubt about how to correctly handle this kind of situation:
I have a repository implemented by an interface like this in which I am defining my "query methods":
public interface ExcelRepository extends CrudRepository<Country, Integer> {
public List<Country> findAllByOrderByCountryNameAsc();
public List<BlockAttackDetail> findAllByOrderByAttackTypeAcronymAsc();
}
As you can see I am extending the CrudRepository interface and I specified a single model class named Country mapping a specific table on my database.
I added a second method working on another entity class (BlockAttackDetail) mapping a different database table.
So starting my application I obtain this error because this repository is intended only for the database table mapped by the Country entity class:
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property attackTypeAcronym found for type Country!
My problem is: Do I have to create multiple JPA repositories interfaces (one for each entity) or exist a way to have a single JPA repository interface working on multiple entity classes?
In my specific case I will have few methods that will interact with a specific datbase table (with an entity) and I prefear have a single repository interface handling multiple entity classes (to avoid confusion).
Can I do it in some way? And if I can do it make it sense from an architectural point of view or it is better have a specific JPA repository interface for each entity class?
According to Spring Data documentation you should have one repository for each entity
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.definition
In your case the only way to do your job is with Spring Data JPA:
public interface CountryRepository extends CrudRepository<Country, Integer> {
public List<Country> findAllByOrderByCountryNameAsc();
}
public interface BlockAttackDetailRepository extends CrudRepository<BlockAttackDetail, Integer> {
public List<BlockAttackDetail> findAllByOrderByAttackTypeAcronymAsc();
}
I don't know why Spring doesn't like my code:
I have Entry.java:
#Entity
#Table(name = "entries")
public class Entry {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "text")
private String text;
}
EntryDao.java:
public interface EntryDao extends JpaRepository<Entry, Long> {
List<Entry> findAllEntries();
}
EntryService.java:
#Service
public interface EntryService {
List<Entry> findAllEntries();
}
EntryServiceImpl.java:
public class EntryServiceImpl implements EntryService {
private EntryDao entryDao;
private SessionFactory sessionFactory;
#Override
#SuppressWarnings("unchecked")
public List<Entry> findAllEntries() {
Session session = this.sessionFactory.getCurrentSession();
List<Entry> entries = session.createQuery("from entries").list();
return entries;
}
}
This code gives me an error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entryDao': Invocation of init method failed; nested exception is org.springframework.data.mapping.PropertyReferenceException: No property findAllEntries found for type Entry!
I don't understand how to handle this error and why this error occurs.
The root cause you got the exception is that you're against the convention/rules to declare/create queries in Spring Data JPA.
The official docs of Spring Data JPA mentioned that:
The goal of Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores.
The central interface of abstraction is Repository, to manage your entity, you need to declare your own interface of Repository and JPA will help you to create proxy instances for those interfaces. There're already some base Repositories like CrudRepository or PagingAndSortingRepository to provide basic functionalities as you can tell from their names, so by extending those basic ones, you'll have many basic methods. To define more specific access methods, you need to follow the ways JPA provided to create queries:
Define method in your interface following the method name convention
Use #Query annotation to define it manually
For the first method, the docs of Query Create has detailed clarification, here's some key idea:
The mechanism strips the prefixes find…By, read…By, query…By, count…By, and get…By from the method and starts parsing the rest of it. The introducing clause can contain further expressions such as a Distinct to set a distinct flag on the query to be created. However, the first By acts as delimiter to indicate the start of the actual criteria. At a very basic level you can define conditions on entity properties and concatenate them with And and Or
Simply speaking, JPA will parse the method name and try to find the related property to create query criteria for you.
Now let's have a look at your code, if you just want to retrieve all of your entities, you don't need to define your own method, there's already findAll methods pre-defined, if you want to retrieve entities based on text content, it's supposed to look like:
Entity findByText(String text)
but your method findAllEntites just don't match any rules, so JPA throws such an error message to you.
As #AbdullahWasi said, just use the existing findAll() method from SpringData for your code. You might want to place a #Transactional annotation in your code, but that depends on your transaction boundaries.
Just remove your custom method from your Dao.
public interface EntryDao extends JpaRepository<Entry, Long> {
}
And use the default spring data findAll
#Transactional
public class EntryServiceImpl implements EntryService {
private EntryDao entryDao;
private SessionFactory sessionFactory;
#Override
#SuppressWarnings("unchecked")
public List<Entry> findAllEntries() {
return entryDao.findAll();
}
}
Does an equivalent for the Hibernate filters exist in the JPA?
The following hibernate annotation can be for example used in order to define a filter:
#Entity
#FilterDef(name="minLength", parameters=#ParamDef( name="minLength", type="integer" ) )
#Filters( {
#Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length"),
#Filter(name="minLength", condition=":minLength <= length")
} )
public class Forest { ... }
I would like to use something equivalent from JPA in order to restrict read access to some entities. How it can be done using clean JPA, without Hibernate annotations?
I didn't find any serious and reliable solution.
I analysed the "JPA Security" project. However, its home page was last updated two years ago, its last version is 0.4.0 and it doesn't seem to be a reliable solution. It's not a standard and it is not popular.
Other alternative in Hibernate which can be used in my case to restrict read access to an entity is the Hibernate Interceptor API - the following interface method can be implemented in order to append a SQL string which contains some additional conditions:
org.hibernate.Interceptor.onPrepareStatement(String sql)
or the following method can be overriden:
org.hibernate.EmptyInterceptor.onPrepareStatement(String sql)
I found out that there are some JPA event callbacks and annotations, e.g. #PostLoad. However, none of these can be used in my case, because I need something to restrict access to entities based on some conditions (user role).
Anyone knows how it can be done using JPA standards?
It seems to me that you are attempting to perform validations on entity objects. You have a few options to accomplish this.
The first would be to use the Java Validations API and its associated validations. This is the recommended approach with Java EE, of which JPA is a part. For example, you could write your entity class as follows:
#Entity
class Person {
#NotNull
#Size(min = 5, Max = 50)
String name;
}
Now, every time you attempt to persist an instance of Person, the JPA provider will automatically validate the instance, provided there is a Java Validator on the classpath. Validation errors will be thrown as runtime exceptions and can be used to rollback transactions. It is also possible to invoke a validator manually, collect any validation errors and transform them into user-friendly messages if required.
The other (probably dirty) option is to use the JPA Event Listeners, perform validations and throw an exception if a validation fails. This will terminate the JPA operation immediately and rollback any transactions.