Spring dynamic JPA repository type - java

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.

Related

Return #id fields from Spring Data JpaRepository

I have inherited a MASSIVE Java application and am having quite a few problems trying to find my way around it. I have a specific problem regarding Spring JpaRepository. Please note that I have just started in Spring and am not that sure footed yet.
I have a repository with the #RepositoryRestResource annotations.
#RepositoryRestResource
public interface GoodsReceiptRepository extends JpaRepository<GoodsReceipt, GoodsReceiptId> {
I have the #Entity as well:
#Entity
#Table(name = AvisoPosition.TABLE)
#IdClass(AvisoPositionId.class)
public class AvisoPosition implements Serializable {
and
#Entity
#Table(name = GoodsReceipt.TABLE)
#IdClass(GoodsReceiptId.class)
public class GoodsReceipt implements Serializable {
any fields I have with the #Id annotation are not returned in the JSON response:
#Id #Column(name = "LGNTCC")
private String accountingArea;
How do I get these ID fields?
If I remove the #Id I get what I want but I do not dare do that as I cannot judge what effect that will have on the application.
Cheers
It seems that you're using Spring Data Rest what you're seeing is the default behavior for it.
You can customize this behavior by doing the following:
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
#Configuration
public class RepositoryConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(YourClassNameGoesHere.class);
}
}
Try to create a DTO for each entity, then avisoPostionDTO.setId(avisoPosition.getId()); and you just return the DTO of each entity.

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type Account to type AllAccount

I am using SpringBoot and JPA to call db,
I am getting exception as
org.springframework.core.convert.ConverterNotFoundException: No
converter found capable of converting from type
[com.xxx.central.model.Account] to type
[com.xxx.central.model.AllAccount]
Below is my code
Account.java
#Entity
#Table(name = "account")
public class Account implements Serializable {
AllAccount.java
#Entity(name="allAccounts")
#Table(name = "account")
public class AllAccount implements Serializable {
AccountRepository.java
#RepositoryDefinition(domainClass = Account.class, idClass = Integer.class)
public interface AccountRepository extends CrudRepository<Account, Integer>
{
public List<AllAccount>
findByIsActiveAndClientNameIgnoreCaseContainingOrderByAccountCreatedDesc(
Boolean active, String clientNameSearchString);
}
When i am calling above repository from my service class i getting exception.
Where i am going wrong?
Thank you.
public interface AccountRepository extends CrudRepository<Account, Integer>
This line makes your Repository class only return types of object Account.
That's why when you call
public List<AllAccount>
findByIsActiveAndClientNameIgnoreCaseContainingOrderByAccountCreatedDesc
it tries to covert from type Account to AllAccount which it can't and hence the exception.
Either you create a different repository class for AllAccount or change this one to return AllAccount by changing to
public interface AccountRepository extends CrudRepository<AllAccount, Integer>

(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 Data JPA - insert a base class into the repository

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.

ManagedBeanCreationException

I have Users pojo and this pojo is not extend from T.It is like this
#Entity
#Table(name = "USERS")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "USERS.findAll", query = "SELECT s FROM USERS s")})
public class USERS implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected USERSPK usersPK;
#Lob
#Column(name = "Name")
private Stirng name;
#Size(max = 20)
#Column(name = "Surname")
private String surname;
And I wanted to select smth.from this table.Service and DAO classes are below:
public interface CommonService {
public List<Object> hepsiniGetir2(Class persistenceClass, String property, Object searchCrit);
}
This is implementation of interface;
#Service("commonService")
public class CommonServiceImpl implements CommonService, Serializable {
#Transactional
public List<Object> hepsiniGetir2(Class persistenceClass, String property, Object searchCrit) {
return commonDao.findAllByCrit2(persistenceClass, property, searchCrit);
}
}
And here dao interface:
public interface CommonDAO extends GenericDAO<TemelNesne, Long> {
public List<Object> findAllByCrit2(Class persistenceClass, String property, Object searchCrit);
}
Here implementation of dao class:
#Repository
public class CommonDAOImpl extends GenericDAOImpl<TemelNesne, Long> implements CommonDAO {
public List findAllByCrit2(Class persistenceClass, String property, Object searchCrit) {
Criteria c = sessionFactory.getCurrentSession().
createCriteria(persistenceClass).add(Restrictions.eq(property, searchCrit));
List<Object> list = c.list();
if (list.isEmpty()) {
return null;
} else {
return list;
}
}
}
In view class I call this method like ;
#ManagedBean(name="userView", eager=true)
#ViewScoped
public class UserView extends BaseView implements Serializable {
#ManagedProperty("#{commonService}")
private CommonService commonService;
private List<USERS> list;
#PostConstruct
public void init() {
list = (List) commonService.hepsiniGetir2(USERS.class, "name", "Deniz");
}
}
Lastly, I had this exception:
Tem 04, 2014 5:38:10 PM
com.sun.faces.application.view.FaceletViewHandlingStrategy
handleRenderException SEVERE: Error Rendering View[/userList.xhtml]
com.sun.faces.mgbean.ManagedBeanCreationException: An error occurred
performing resource injection on managed bean userView Caused by:
org.hibernate.type.SerializationException: could not deserialize
Caused by: java.io.StreamCorruptedException: invalid stream header:
3C3F786D
Your problem is a concept problem. JSF beans cannot autowire Spring beans, nor the other way around. This is because they are managed in different containers. You should integrate Spring with JSF to allow injection, which basically boils down to Spring controlling everything. There are plenty tutorials in the net about doing this. One good tutorial is mkyong's: JSF 2 + Spring 3 Integration Example. Basically, these are the steps (taken from his tutorial):
In faces-config.xml file, add Expression Language (EL) resolver:
<application>
<el-resolver>
org.springframework.web.jsf.el.SpringBeanFacesELResolver
</el-resolver>
</application>
Instead using JSF annotations, use Spring annotations for JSF managed beans. Example applied to your classes:
#Component("userView")
#Scope("view")
public class UserView extends BaseView implements Serializable {
#Autowired
private CommonService commonService;
private List<USERS> list;
#PostConstruct
public void init() {
list = (List) commonService.hepsiniGetir2(Sertifikalar.class, "name", "Deniz");
}
}
Note that in this example, I'm using #Scope("view") but Spring doesn't have a view scope by default, the team is still working on it. You have to implement this scope manually. Fortunately, you can use Cagatay's implementation to solve this.
Apart of these problems, you have another conceptual problem: the only bean that supports eager=true is #ApplicationScoped since it will work as a #Singleton Spring bean, other managed beans will ignore this attribute at all.

Categories

Resources