Spring Data JPA - insert a base class into the repository - java

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.

Related

(CRUD) Repository for a large number of JPA classes

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);

Spring dynamic JPA repository type

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.

Create a class that can handle all type of CRUD Operation. No need to make SessionFactory or Session Object Each Time I need to do CRUD

I want to make a class in java have a method that can interact with Hibernate configuration and do certain operation is identified as ENUM (eg: read, update, add, delete and etc.)
Method Parameters should be (Enum operations, class DTO, NamedQuery namedquery ,DTOObject Object_to_persist, param(any extra argument)).
Method should be as convenient that i can call it whenever i need by Passing actual argument(Operation.read, USERDTO.class , namedquery , USERDTO obj_UserDTO , HashMap hmapData).
/* Enum Defined Operation done to the database.
*/
public enum Operations {READ,UPDATE,ADD,DELETE};
/*Centralized Method Defination for Basic CRUD Operation */
public T<?> DatabaseCRUDOperations((Operation.READ,USERDTO.class , namedquery , USERDTO obj_UserDTO , HashMap<String, String> hmapid){
switch(Operation opts){
case Operation.Read : //Call Read Method
break;
case Operation.UPDATE: //call Update Method
break;
......
......
default://call any Error Method or set error
}
}
Basically i want to define a custom Class (one kind of internal Framework for project) which have all basic CRUD Operation should done via this Class only. No need to create SessionFactory or Session Object Create every Where i Need.
Please Suggest by some code-snipt.
Java Generics to the rescue! Prepare to be amazed.
Your abstract entity (useful if you want to define methods to use in things like, for example, generic controller classes):
public abstract class AbstractEntity<ID extends Serializable> implements Serializable {
public abstract ID getPrimaryKey();//I simply put this here as an example
}
Create the generic DAO interface:
public interface IGenericDAO<T extends AbstractEntity<ID>, ID extends Serializable> {
T findByPrimaryKey(ID id);
T save(T entity);
void delete(T entity);
List<T> saveAll(List<T> entities);
.
.
.
}
Then, define your abstract generic DAO:
public abstract class AbstractHibernateDAO<T extends AbstractEntity<ID>, ID extends Serializable> implements IGenericDAO<T, ID> {
protected Class<T> persistentClass;
protected AbstractHibernateDAO(){}
#SuppressWarnings({ "unchecked", "rawtypes" })
public AbstractHibernateDAO(Class c) {
persistentClass = c;
}
#Override
#SuppressWarnings("unchecked")
public T findByPrimaryKey(ID id){
return (T) HibernateUtil.getSession().get(persistentClass, id);
}
#Override
public T save(T entity){
HibernateUtil.getSession().saveOrUpdate(entity);
return entity;
}
#Override
public List<T> saveAll(List<T> entities){
for(int i = 0; i < entities.size(); i++){
HibernateUtil.getSession().saveOrUpdate(entities.get(i));
}
return entities;
}
#Override
public void delete(T entity){
HibernateUtil.getSession().delete(entity);
}
.
.
.
}
DAO interface for entity (For this example, I'm using Integer as the primary key):
public interface IMyEntityDAO extends IGenericDAO<MyEntity, Integer> {
}
Now (drum roll), you are ready to lay some concrete (classes)...
DAO:
public class MyEntityDAO extends AbstractHibernateDAO<MyEntity, Integer> implements IMyEntityDAO {
public MyEntityDAO() {
super(MyEntity.class);
}
}
MyEntity:
#Entity
#Table(name = "my_entity_table")
public class MyEntity extends AbstractEntity<Integer>{
#Id
#Column(name = "index")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
public Integer getPrimaryKey (){
return id;
}
.
.
.
}
Boom. (Mic drop)
Let me know if you need further explanation!
I can't rightfully post this without giving credit to Cameron McKenzie and his amazing book here . Which opened my eyes to a whole new world realizing the power of generics.

Spring Data MongoDB Repository with custom collection name

I am using Spring Data for MongoDB and I need to be able to configure collection at runtime.
My repository is defined as:
#Repository
public interface EventDataRepository extends MongoRepository<EventData, String> {
}
I tried this silly example:
#Document(collection = "${mongo.event.collection}")
public class EventData implements Serializable {
but mongo.event.collection did not resolve to a name as it does with a #Value annotation.
A bit more debugging and searching and I tried the following:
#Document(collection = "#{${mongo.event.collection}}")
This produced an exception:
Caused by: org.springframework.expression.spel.SpelParseException: EL1041E:(pos 1): After parsing a valid expression, there is still more data in the expression: 'lcurly({)'
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.doParseExpression(InternalSpelExpressionParser.java:129)
at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:60)
at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:32)
at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpressions(TemplateAwareExpressionParser.java:154)
at org.springframework.expression.common.TemplateAwareExpressionParser.parseTemplate(TemplateAwareExpressionParser.java:85)
Perhaps I just don't know how to quite use SPel to access values from Spring's Property Configurer.
When stepping through the code, I see that there is a way to specify collection name or even expressions, however, I am not sure which annotation should be used for this purpose or how to do it.
Thanks.
-AP_
You can solve this problem by just using SPeL:
#Document(collection = "#{environment.getProperty('mongo.event.collection')}")
public class EventData implements Serializable {
...
}
Update Spring 5.x:
Since Spring 5.x or so you need an additional # before environment:
#Document(collection = "#{#environment.getProperty('mongo.event.collection')}")
public class EventData implements Serializable {
...
}
Docs:
SpEL: 4.2 Expressions in Bean Definitions
SpEL: 4.3.12 Bean References
PropertyResolver::getProperty
So, at the end, here is a work around that did the trick. I guess I really don't know how to access data from Spring Properties Configurer using the SPeL expressions.
In my #Configuration class:
#Value("${mongo.event.collection}")
private String
mongoEventCollectionName;
#Bean
public String mongoEventCollectionName() {
return
mongoEventCollectionName;
}
On my Document:
#Document(collection = "#{mongoEventCollectionName}")
This, appears to work and properly pick up the name configured in my .properties file, however, I am still not sure why I could not just access the value with $ as I do in the #Value annotation.
define your entity class like
#Document(collection = "${EventDataRepository.getCollectionName()}")
public class EventData implements Serializable {
Define a custom repository interface with getter and setter methods for "collectionName"
public interface EventDataRepositoryCustom {
String getCollectionName();
void setCollectionName(String collectionName);
}
provide implementation class for custom repository with "collectionName" implementation
public class EventDataRepositoryImpl implements EventDataRepositoryCustom{
private static String collectionName = "myCollection";
#Override
public String getCollectionName() {
return collectionName;
}
#Override
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}
Add EventDataRepositoryImpl to the extends list of your repository interface in this it would look like
#Repository
public interface EventDataRepository extends MongoRepository<EventData, String>, EventDataRepositoryImpl {
}
Now in your Service class where you are using the MongoRepository set the collection name, it would look like
#Autowired
EventDataRepository repository ;
repository.setCollectionName("collectionName");
Entity Class
#Document // remove the parameters from here
public class EscalationCase
{
}
Configuration class
public class MongoDBConfiguration {
private final Logger logger = LoggerFactory.getLogger(MongoDBConfiguration.class);
#Value("${sfdc.mongodb.collection}") //taking collection name from properties file
private String collectionName;
#Bean
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoMappingContext context) {
MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), context);
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory, converter);
if (!mongoTemplate.collectionExists(collectionName)) {
mongoTemplate.createCollection(collectionName); // adding the collection name here
}
return mongoTemplate;
}
}

Best way of handling generic entities in Spring MVC(Generics, Reflection, or other)?

I have an entity named Commercial. I have an Category entity where the list of commercial categories are hold. For each category there is an separate entity extending Commercial(like RestaurantCommercial, PhotoStudioCommercial etc. total up to 20 entities) with JOINED inheritance strategy.
Commercial entity holds up general properties like title, text contactnumber of some company's commercial, while RestaurantCommercial and PhotoStudioCommercial holds additional specific properties concerned with that category.
The problem is that writing a separate dao and controller for each entity is a bit plenty of work, so I am searching for a neat way to handle this issue.
I need an unified controller and may be the DAO for handling the form control and persisting new instances of the entities that extend Commercial.
Here is approximately what I was thinking about:
#RequestMapping(value={"/vendor/commercial/{categoryName}/new"}, method=RequestMethod.GET)
public String showNewCommercialForm(#PathVariable("categoryName") String categoryName,
Map<String, Object> map) {
Category category = categoryDAO.getCategoryByServiceName(categoryName);
Class clazz = Class.forName(category.getClassName()); //here className is smth like domain.commercial.RestaurantCommercial
map.put("commercialInstance", clazz.newInstance());
//separate view for each category of commercial
return "vendor/commercial/"+categoryName+"/new";
}
And I was thinking for a similar controller for saving form data even if I would have to write a sperate binder for this stuff.
So the question is: What would you suggest to handle this issue or what would be the best practice if you had already faced similar need(Generics, Reflection or smth else)? Or if that would be worthy or not and why?
Thanks in advance.
I create a Dao interface for such cases:
public interface Dao<T extends Commercial> {
T getAll();
}
After that an abstract Dao implementation, for example hibernate based:
public CommonHibernateDao<T extends Commercial> implements Dao<T>
{
private final Class<T> entityType;
protected CommonHibernateDao(Class<T> entityType) {
this.entityType = entityType;
}
public List<T> getAll() {
// hibernate get all implementation
}
}
And RestaurantCommercial Dao interface and implementation:
public interface RestaurantCommercialDao extends Dao<RestaurantCommercial> {
}
public class HibernateRestaurantCommercialDao extends CommonHibernateDao<RestaurantCommercial> implements RestaurantCommercialDao {
public HibernateRestaurantCommercialDao() {
super(RestaurantCommercial.class);
}
}
All implementation goto CommonHibernateDao. In it's subclasses only constructor calling neaded. Basically you can do it with reflection but as for me it is not clear.
For controller (something like RESTfull API):
#Controller
public class YourController() {
#RequestMapping(value = "/restapi/{entityType}")
public String postEntity(HttpServletRequest request, #PathVariable(value = "entityType") String entityType) {
// If enetity name will be packegae + class name
Object beanInstance = Class.forName(entityName);
ServletRequestDataBinder binder = new ServletRequestDataBinder(beanInstance);
binder.bind(request);
// Here by type of entity you can get DAO and persist
}
}
If form input names will be same to your bean names - binding will do all automatically.

Categories

Resources