I can create a repository via defining an interface on the appropriate JPA class A like the following:
public interface ARepository extends CrudRepository<A, Long>
{
}
and I can use that in my Controller (for example) via
#Autowired
private ARepository aRepository;
and just can do things like this:
aRepository.save(..);
aRepository.findAll();
..
No problem so far.
But my problem is that I have ca. 500 JPA classes and need to access each table which means to define 500 Repositories in the style of above.
So does exist an thing to create that either dynamically via some Spring Data "magic" which from my point of view should exist otherwise the above would not be possible. It looks like this is similar to my problem.
Apart from that one more issue related to the above. I can define findBy... methods in the interface and in the background there will be generated a query method for this particular attribute. The question is also if this can be done in a dynamic way related to the previous question, cause I have groups of tables which need supplemental query methods..
There is spring-data-generator which can automatically generate the interfaces for you.
Regarding your 2nd question I don't think you that can be done in a dynamic way. Java is statically compiled and there's no way to add members dynamically. There could be a tool that generates code for those methods but if that tool generates methods for all combinations of columns you will end up with a huge amount of methods.
You can make a base abstract entity for your 500 classes an then create one repo for this class. (I think it's a common practice to have a BaseEntity class with id, version etc. for every entity in the project).
For simple repo methods (like save, findAll etc.) it will work right from the box (note - entities must have the equal id type). For example:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstarct class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
}
#Entity
public class Entity1 extends BaseEntity {
private String name;
}
#Entity
public class Entity2 extends BaseEntity {
private String name;
}
public interface BaseEntityRepo extends JpaRepository<BaseEntity, Long> {
}
Note that BaseEntity must have #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) to prevent of using singe table base_entity for every entity. And their ids must not intersect (see #GeneratedValue(strategy = GenerationType.SEQUENCE)).
Usage:
#RunWith(SpringRunner.class)
#SpringBootTest
public class BaseEntityRepoTest {
#Autowired private BaseEntityRepo repo;
#Before
public void setUp() throws Exception {
repo.save(asList(
new Entity1("entity1"),
new Entity2("entity2")
));
}
#Test
public void readingTest() throws Exception {
List<BaseEntity> entities = repo.findAll();
assertThat(entities).hasSize(2);
}
}
Related to your second question you can use this approach:
public interface BaseEntityRepo extends JpaRepository<BaseEntity, Long> {
<T> T findById(Long id, Class<T> type);
}
Usage:
#Test
public void findById() {
final Entity1 entity1 = repo.findById(1L, Entity1.class);
final Entity2 entity2 = repo.findById(2L, Entity2.class);
assertThat(entity1).isNotNull();
assertThat(entity2).isNotNull();
}
But you can build repo query methods only for 'common' properties of inherited entities which are present in the base class. To make this method work you must move the name parameter to the BaseEntity:
<T> List<T> findAllByNameLike(String name, Class<T> type);
Related
I have two entities
#Entity
#Table(name = "view_a")
public class A extends BaseStringIdTableClass
#Entity
#Table(name = "view_b")
public class B extends BaseStringIdTableClass
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseStringIdTableClass implements Serializable {
#Id
private String id;
And in the database i have two views
select * from view_a
|ID|ColumnA|....
|34222|some Value|....
select * from view_b
|ID|ColumnB|...
|34222|lla lla|...
I have therefore in the database different views. But the rows in this different views have the same ID.
Now i try to read the entities with the standard CRUD Repository.
A a = aRepository.findById("34222").get();
B b = bRepository.findById("34222").get();
In this case i can not find entity b. If i swop the two lines of code i can not find entity a.
I think the persistence context can at one time containt for a specific ID only one entity? Is this right. What can i do?
Repository definitions
public interface ARepository extends JpaRepository<A, String>, QuerydslPredicateExecutor<A> {
public interface BRepository extends JpaRepository<B, String>, QuerydslPredicateExecutor<B> {
Sleeping over one night always help....First sorry for my initial incomplete question.
The problem/error was the both entities extended the same abstract class. And in this abstract class the ID was definied.
Fix after this recogniton was easy. One of the entities does not extend my abstract class, but definies his own id. And now it works......
I'm having trouble to grasp, use this correctly.
I have a AbstractEntity/AbstractGraphEntity class like this:
public abstract class GraphEntity extends AbstractEntity {
#GraphId
#Getter #Setter
#JsonIgnore
protected Long nodeId;
#Getter #Setter
#Index(unique = true, primary = true)
protected String id;
}
And a BaseGraphRepository like this:
#NoRepositoryBean
public interface BaseGraphRepository<T extends GraphEntity> extends GraphRepository<T> {
T findById(String id);
Collection<T> findByIdIn(Collection<String> ids);
T deleteById(String id);
Collection<T> deleteByIdIn(Collection<String> ids);
}
The idea is to have multiple entities that are extenting GraphEntity, and use several repositories extending the BaseGraphRepository.
But each time I query on: findById, it keeps giving me null.
So I've tried using, GraphEntity as a #NodeEntity, but that's a terrible idea, since then if I do query for an ID of one kind of entity, let's say a Category, but on an ProductRepository, it will give me the GraphEntity object.
So how should I approach this ?
Thanks!
I've managed to solve the findById/findByIdIn using the Neo4jRepository as such:
public interface BaseGraphRepository<T extends GraphEntity> extends Neo4jRepository<T, String> {}
But even with the Neo4jRepository the Generated Cypher for a custom query with the GraphEntity would be wrongly generated.
MATCH (n:GraphEntity) WHERE n.`id` ...
There might be some workaround to this, or it's a bad practice, I'm not sure.
I have about 30 tables that I need to fill from an XML file. And I want to use JPA for that purpose.
Now I have 30 classes annotated with #Entity, config that scans entities and repositories;
Also I have:
#Repository
public interface MyRepository extends JpaRepository<MyEntity1, Long> {
}
And (some controller):
#Autowired
public MyRepository myRepository;
...
...
MyEntity1 entity = new MyEntity(...);
myRepository.save(entity);
It works fine with one #Entity but should I define 30 repositories for that?
I thought I could do something like this:
#Repository
public interface MyRepository<T> extends JpaRepository<T, Long> {
}
and then:
#Autowired
public MyRepository<MyEntity1> myRepository1;
#Autowired
public MyRepository<MyEntity2> myRepository2;
but that gave an error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myRepository1': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not a managed type: class java.lang.Object
Try this approach:
Base class for all entities:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
}
Entities:
#Entity
public class Entity1 extends BaseEntity {
private String name;
}
#Entity
public class Entity2 extends BaseEntity {
private String name;
}
A common repo:
public interface BaseEntityRepo extends JpaRepository<BaseEntity, Long> {
}
Usage:
public class BaseEntityRepoTest extends BaseTest {
#Autowired
private BaseEntityRepo repo;
#Test
public void baseEntityTest() throws Exception {
BaseEntity entity1 = new Entity1("entity1");
BaseEntity entity2 = new Entity2("entity2");
repo.save(entity1);
repo.save(entity2);
List<BaseEntity> entities = repo.findAll();
assertThat(entities).hasSize(2);
entities.forEach(System.out::println);
}
}
Unfortunately you can't do this and you will have to write 30 separate repositories. You can however write generic repositories when the entities share a single table inheritance. (See the answer to Using generics in Spring Data JPA repositories)
What your code is trying to do is make a repository where the shared inheritance is on the class Object which isn't an #Entity hence the exception.
Also an additional minor note, you don't need to annotate your repositories with #Repository. Spring data automatically registers these as beans if it is configured correctly.
As far as I am aware what you are trying is not possible. Spring Data JPA needs an interface per Entity type for its repositories, Because Spring Data JPA will be creating the query implementations.
So it is advised that you have a Repository per Entity as it will allow you to add complex findByXXX methods in the future also.
I have a small application being a bridge between RabbitMQ and SQL database. It is meant to consume events (of few types) from the queue and store them in appropriate tables in the DB. In majority of cases, there is almost no processing between the Event and the Entity (just field copying). This is the reason why I have injected a Dozer mapper that makes the conversion - it works flawlessly. The difficult part is saving a generic object to the repository without having to use switch + instanceof as a last resort.
Code:
#Service
public class EventProcessorImpl implements EventProcessor {
#Autowired
private Mapper mapper; // a Dozer mapper instance
#Override
public void process(final BaseEvent event) {
final BaseEntity entity = mapper.map(event, BaseEntity.class);
// TODO save the entity to the database after it's transformed
}
}
The BaseEntity is a base class for entites, as follows:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity {
#Id
private String guid;
public String getGuid() {
return guid;
}
public void setGuid(final String guid) {
this.guid = guid;
}
}
#NoRepositoryBean
public interface BaseRepository<T extends BaseEntity>
extends CrudRepository<T, String> {
}
#Repository
public interface EmailOpenedRepository
extends BaseRepository<EmailOpened> {
}
The question is - how to save the entity, given that:
it is passed as a base class (what I consider as an advantage, but this can be changed)
there are quite a couple of event types (now 5, but can explode to 30 in a few months)
What I have tried:
I have #Autowired an instance of BaseRepository and tried calling repository.save(entity), but it fails on app startup due to multiple bean definitions available.
Based on the question, I have successfully implemented the following, but I don't know whether this is a correct approach:
public void process(final BaseEvent event) {
final BaseEntity entity = mapper.map(event, BaseEntity.class);
final CrudRepository repository = (CrudRepository) new Repositories(context)
.getRepositoryFor(entity.getClass());
repository.save(entity);
}
I thought of iterating over all available beans of BaseRepository and finding the one that will support this type (findFirst(repository -> repository.supports(entity.getType())), but Spring Data JPA repositories are interfaces and I cannot store the supported type in the interface.
Suppose a Table per subclass inheritance relationship which can be described bellow (From wikibooks.org - see here)
Notice Parent class is not abstract
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
public class Project {
#Id
private long id;
// Other properties
}
#Entity
#Table(name="LARGEPROJECT")
public class LargeProject extends Project {
private BigDecimal budget;
}
#Entity
#Table(name="SMALLPROJECT")
public class SmallProject extends Project {
}
I have a scenario where i just need to retrieve the Parent class. Because of performance issues, What should i do to run a HQL query in order to retrieve the Parent class and just the Parent class without loading any subclass ???
A workaround is described below:
Define your Parent class as MappedSuperClass. Let's suppose the parent class is mapped To PARENT_TABLE
#MappedSuperClass
public abstract class AbstractParent implements Serializable {
#Id
#GeneratedValue
private long id;
#Column(table="PARENT_TABLE")
private String someProperty;
// getter's and setter's
}
For each subclass, extend the AbstractParent class and define its SecondaryTable. Any persistent field defined in the parent class will be mapped to the table defined by SecondaryTable. And finally, use AttributeOverrides if needed
#Entity
#SecondaryTable("PARENT_TABLE")
public class Child extends AbstractParent {
private String childField;
public String getChildProperty() {
return childField;
}
}
And define another Entity with the purpose of retrieving just the parent class
#Entity
#Table(name="PARENT_TABLE")
#AttributeOverrides({
#AttributeOverride(name="someProperty", column=#Column(name="someProperty"))
})
public class Parent extends AbstractParent {}
Nothing else. See as shown above i have used just JPA specific annotations
Update: It appears the first option doesn't work as I thought.
First option:
Specify the class in the where clause:
select p from Project p where p.class = Project
Second option:
Use explicit polymorphism that you can set using Hibernate's #Entity annotation:
#javax.persistence.Entity
#org.hibernate.annotations.Entity(polymorphism = PolymorphismType.EXPLICIT)
#Inheritance(strategy = InheritanceType.JOINED)
public class Project {
#Id
private long id;
...
}
This is what Hibernate Core documentation writes about explicit polymorphism:
Implicit polymorphism means that
instances of the class will be
returned by a query that names any
superclass or implemented interface or
class, and that instances of any
subclass of the class will be returned
by a query that names the class
itself. Explicit polymorphism means
that class instances will be returned
only by queries that explicitly name
that class.
See also
How to get only super class in table-per-subclass strategy?
Actually, there is a way to get just the superclass, you just need to use the native query from JPA, in my case I'm using JPA Repositories it would be something like that:
#Query(value = "SELECT * FROM project", nativeQuery = true)
List<Resource> findAllProject();
The flag nativeQuery as true allow running the native SQL on database.
If you are using Entity Manager check this out: https://www.thoughts-on-java.org/jpa-native-queries/