As JPA and Spring have different context management, it is not recommended to create a data object class with both annotations #Component and #Entity.
But without #Component data object can't be injected into a service by #Autowired.
But creating new instance of my data object with new seems like a regression for me.
Is there a good way to inject a data object (#Entity) in a spring managed service ?
Data object :
#Component
#Entity
#Table(name = "user")
public class UserDo {
//data object stuff ...
Service :
#Service("listAllGoods")
#Transactional(propagation = Propagation.REQUIRED)
public class ListAllGoods implements IListGoodService{
#Autowired
private IGoodDao goodDao;
#Autowired
private UserDo user;
//option 1 : works but not recommended because forces #Component on data object
#Override
public List<GoodDo> createGood() {
user.setName("Roger");
return goodDao.create(user);
}
//option 2 :
// without #Autowired UserDo
// regression feeling
#Override
public List<GoodDO> createGood() {
UserDo user = new UserDo();
user.setName("Roger");
return goodDao.create(user);
}
The main feature of Spring is dependency injection.
Dependency or coupling, a state in which one object
uses a function of another object
It's clear that User entity is not a dependency in your situation, so it's the most correct approach to create it with a new operator.
Also, you said that you want your "dependency" to be created every time you reference your service. It's the "How to update prototype bean in a singleton" problem which you can encounter on an interview. It's not in the scope of your question, but I highly recommend you to google this.
Related
I'm making a small program using Spring, Maven and Hibernate. The current goal is to use JpaRepository to interact with a Postgresql database. However, when I try to call for it to list all entries in a table within the database, it spits out a NullPointerException. Online resources vary in their implementation, so it's been hard for me to understand what goes wrong.
My application can be summarized as follows:
Javatest3Application.java (Outermost layer, handles communication)
#SpringBootApplication
#EnableAutoConfiguration
#EnableJpaRepositories
#RestController
public class Javatest3Application {
//---VARIABLES---
private JavatestService service_handler = new JavatestService();
//---PUBLIC---
public static void main(String[] args) {
SpringApplication.run(Javatest3Application.class, args);
}
#PostMapping("/login")
public ResponseEntity<String> Login(#RequestBody Map<String, String> json_map) {
//>>Read json_map for account_name and account_pwd
//Ask Service layer to log user in
Long session_id = this.service_handler.OpenSession(account_name, account_pwd);
//>>Construct response, blah blah...
}
}
JavatestService.java (Middle layer, manages repository interaction)
#Service
public class JavatestService {
//---VARIABLES---
#Autowired
private JavatestRepository repo;
//---PUBLIC---
public JavatestService() {}
public Long OpenSession(String in_name, String in_pwd) {
//Call database for credentials
List<JavatestUser> user_listings = this.repo.findAll(); //<- THIS FAILS
//>>Go though list, blah blah...
}
}
JavatestRepository.java (Bottom layer, interface extention)
#Repository
public interface JavatestRepository extends JpaRepository<JavatestUser, Long> {
//List<JavatestUser> findAll(); <- Don't think I need to add this. I believe its already in JpaRepository
}
JavatestUser.java (Bottommost layer, DTO class for database entry)
#Entity
#Table(name = "javatest_table", schema = "javatest_schema")
public class JavatestUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long account_id;
private String account_name;
private String account_pwd;
public JavatestUser(){
}
//>>Getter and Setter functions, blah blah...
}
So, as far as I have understood it, we cannot instance objects of an interface. However, when using Spring, the program itself creates classes that implement the interface, and then hands such a derived class back to us via the #Autowired keyword.
Thus when we call the findAll() function, we use that derived class to fetch objects of the associated #Entity class.
From my research I've come to believe I might use the #Service keyword wrong, and that it perhaps should be a #Controller. However, as far as I can see, the are implementations of both alternatives, so my understanding of what differentiates them is somewhat lacking. Regardless of which I am using, the #Autowired doesn't seem to provide any JavatestRepository-derived object for me to call findAll() upon.
EDITS
Added #EnableJpaRepositories in accordance with Eugene Botyanovsky's suggestion.
You are probably missing annotation, which enables all your repositories:
#EnableJpaRepositories
It's similar to #EnableAutoConfiguration you used, but exactly for JPA repositories.
This question already has answers here:
Spring JPA entities not saving to database
(3 answers)
Closed 2 years ago.
I have a server using spring boot and spring data jpa.
I have two classes annotated with #RestController in my server. One of them might change entities, while the other won't.
#RestController
#Slf4j
public class ControllerA {
private EntityRepository entityRepository;
public ControllerA(EntityRepository entityRepository) {
this.entityRepository = entityRepository;
}
#PostMapping("/pathStr")
public void changeEntity(#RequestParam("entityId") Long entityId) {
// init
Entity entity = entityRepository.findById(entityId);
// make changes to entity
entity.getOneToOneLinkedEntity().setIntProperty(0);
// save entity
entityRepository.save(entity);
}
}
#RestController
#Slf4j
public class ControllerB {
private Entity cachedEntity;
private EntityRepository entityRepository;
public ControllerB(EntityRepository entityRepository) {
this.entityRepository = entityRepository;
}
#MessageMapping("/otherPath")
public void getEntity(ArgumentType argument) {
if (cachedEntity == null) {
cachedEntity = entityRepository.save(new Entity());
}
Entity entity = entityRepository.findById(cachedEntity.getId()).orElse(null);
int properyValue = entity.getOneToOneLinkedEntity().getIntProperty(); // this is not zero
}
}
Here are the two entities and the repository:
#Entity
public class Entity implements Serializable {
#Id
#GeneratedValue private Long id;
#NotNull
#OneToOne(cascade=CascadeType.ALL)
private OneToOneLinkedEntity linkedEntity;
}
#Entity
public class OneToOneLinkedEntity implements Serializable {
#Id
#GeneratedValue private Long id;
#NotNull
private int intProperty = 0;
}
public interface EntityRepository extends JpaRepository<Entity, Long> {
}
I make a call to ControllerA.changeEntity from the client, and once that returns, I make another call to ControllerB.getEntity. The changes I made in the first call aren't shown in the second call, (and they're not in the database either if I query directly with sql) the int property has an old value. Even though I do the save only on the Entity and not on the linkedEntity, the CascadeType.ALL should make the the linked entity update too, right?
I tried adding an entityRepository.flush() after the save in ControllerA, but the issue still appears. What can I do? How can I make ControllerB get the correct value of intProperty?
This is what I have in application.properties:
spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:mysql://localhost:3306/db_name
spring.datasource.username=user
spring.datasource.password=pass
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy
You should add #Transactional annotation to your method, so spring will handle the transaction and commit your changes.
This usually comes on the #Service class, but I see in your example that you do not have one, so put it on the controller (or add service layer, I think it's better)
You are ignoring dependency injection which is the beauty of spring framework.
Make a Repository class which implements JPARepository and annotate it with #Repository.
Make a Service class and annotate it with #Service and #Transactional.
In Service class autowire the Repository and make corresponding methods call like .save() .find() etc.
In Controller Class autowire the service class and call service method which will call repository methods.
This is all you have to do.
For making your application flow fast, better you create model for the entity class and pass the model between classes instead of entity as it contains a lot more information and thus much more heavy than normal model object.
I'm building a web application using Spring MVC which parses JSON requests into POJOs using #RequestBody/Jackson.
When Jackson creates a POJO, I cannot autowire my DAO service so instead I have created a way to access the DAO statically via a utility method.
private static DAOService daoService;
public static User getUserById(int id)
{
return daoService.getUserDao().getById(id);
}
I have spring populate the daoService on application startup which is just a holder for my DAOs.
I do this because my entities that Jackson creates need to retrieve other child entities from the database to complete itself.
This seems to be working but I'm concerned as to whether or not this is safe. Can anyone foresee any issues with this?
I'm assuming it's safe since daoService is never mutated, and the getById method only acts on its own arguments.
Thanks
Edit:
public void setSlot(int id) {
this.slot = EntityUtils.getSlotById(id);
}
You proposal is valid and safe.
If you want to keep your bean clean of the deserialization process you may create a Jackson converter to convert from Long to your Bean. It requires a bit of plumber but it may worth it:
First annotate your field with a custom converter:
public class Foo {
#JsonDeserialize(converter = SlotConverter.class)
public void setSlot(Slot slot) {
this.slot = slot;
}
}
Then define the converter with the SlotDao annotated with #Autowired. The converter converts from Long to Slot:
public class SlotConverter extends StdConverter<Long, Slot> {
#Autowired
private SlotDao slotDao;
#Override
public Slot convert(Long id) {
return slotDao.getSlotById(id);
}
}
Finally, jackson has to be configured with a custom Spring instanciator. Thus SlotConverter will be instanciated and configured by Spring:
ObjectMapper mapper = new ObjectMapper();
mapper.setConfig(mapper.getDeserializationConfig().with(new SpringHandlerInstantiator(applicationContext.getAutowireCapableBeanFactory())));
The following code will deserialize Foo using the slot id:
Foo foo = mapper.readValue("{\"slot\":10}", Foo.class);
Hope it helps!
I cannot save autowired properties when using Spring components with Neo4j.
I have defined an abstract entity class which includes the graph id:
#NodeEntity
public abstract class DBEntity {
#GraphId private Long id;
}
There is a data model with a parent and a child both defined as Spring components.
I have simplified the data model here, in reality it is a tree with several child nodes and levels:
#Component("DataModel")
#Scope("prototype")
public class DataModel extends DBEntity {
#Autowired private DataStructure dataStructure;
public TestDataStructure testDataStructure;
}
#Component("DataStructure")
#Scope("prototype")
public class DataStructure extends DBEntity {
...
}
For testing purposes, I have also defined a test data structure without Spring annotations:
public class TestDataStructure extends DBEntity {
private String test;
public TestDataStructure() {
this.test = "test";
}
}
Here is also the definition of my repository and the database service implementation:
#Repository
public interface DataModelRepository extends GraphRepository<DBEntity> {
DBEntity save(DBEntity entity);
}
public class DatabaseServiceImpl implements DatabaseService {
#Autowired private DataModelRepository modelRepository;
public void putModel(DBEntity entity) {
modelRepository.save(Entity);
}
}
Now, when I call putModel with an Spring-managed instance of DataModel, it will save the
DataModel instance and also an instance of TestDataStructure as nodes with 1:1 relationship. The instance of DataStructure and the relationship to the instance of DataModel will not be saved, although it is not null in the program.
How can I achieve to save the whole DataModel? Probably Spring interferes with the Neo4j mapping. I am not sure, why autowired properties might get lost in the cloning process or whatever Neo4j uses to get the data. Maybe someone can shed some light on this?
I have a service that I want to #Autowired, but it takes a complex object in constructor. How can I provide it using dependency injection?
#Service
class ProcessService
#Autowired
private PersonService service;
public void run() {
//create the person dynamically, eg based on some user input
Person person = new Person("test");
//new PersonService(person);
//I used to create the object myself. now switching to spring.
//how can I get the person object into the service using autowire?
}
}
#Service
class PersonService {
public PersonService(Person person) {
this.person = person; //the object the service can work with
}
}
You should annotate the costructor of your PersonService with #Autowired.
#Service
class PersonService {
#Autowired
public PersonService(Person person) {
this.person = person; //the object the service can work with
}
}
Then you must provide a Person bean somewhere.
But I guess the Person is an instance that is loaded from a database.
Since a PersonService is a singleton it makes no sense to bind it to one Person instance.
In this case you have to create a new PersonService instance for every Person object that you retrieve from the db.
If you want to do this then it also means that the PersonService is created by your code. Thus out of the spring containers control and therefore spring can not autowire it automatically.
Nevertheless you can use the AutowireCapableBeanFactory to autowire beans that have been instantiated outside the container. But this is a one way. These beans will not be available to those defined in the container.
AutowireCapableBeanFactory acbf = ...;
acbf.autowireBean(someInstance);
When I work with hibernate I usually use a PostLoadListener to autowire domain objects.
But there is also another approach using aspectj and load-wime weaving. Take a look at the spring documentation 8.4.1
If you need to advise objects not managed by the Spring container (such as domain objects typically), then you will need to use AspectJ. You will also need to use AspectJ if you wish to advise join points other than simple method executions (for example, field get or set join points, and so on).
Make a person a #Component and add #ConstructorProperties to the constructor
#Service
class PersonService {
#Autowired
#ConstructorProperties({"person"})
public PersonService(Person person) {
this.person = person; //the object the service can work with
}
}
Just add an empty constructor above the other
#Service
class PersonService {
public PersonService() {
}
public PersonService(Person person) {
this.person = person; //the object the service can work with
}
//Getters and setters
}
Then keep other class and if you want to call the person