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.
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.
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.
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?
The saveUser method doesn't save the user object name change when I have multiple operations inside one method. If I use #Transactional(propagation = Propagation.REQUIRED) on top of the saveUser service method, it works fine. When another class creates a new User object and sets all its values and calls the createUser method, it works fine. Why do I need #Transactional for the saveUser method? In what cases do I need to include #Transactional? I'm using Spring Data and JPA (Hibernate impl). Any ideas?
JPA Entity:
#Entity
public class User{
#Id
#GeneratedValue
private Long id;
#Column
private String name;
//getters/setters..etc
}
Spring Service:
#Service
public class UserServiceImpl{
#Autowired
UserRepository userRepository;
public void saveUser(Long id){
User user = userRepository.findById(id);
user.setName("newname");
userRepository.save(user);
}
public void createUser(User user){
userRepository.save(user);
}
}
Spring Data JPA/Hibernate Impl Repository:
public interface UserRepository extends JpaRepository<User, Long> {
}
The methods in JpaRepository are transactional by default (readonly for retrieving).
Now in your saveUser() method, you need #Transactional because you are retrieving an User by id and updating it and then again persisting to the database. Basically, #Transactional(readOnly=true) is used while reading else #Transactional is used in Spring Data JPA.
User user = userRepository.findById(id);
returns null to user if no user is found and user.setName("newname"); will give NullPointerException.
You need transactions if you update the database state (insert/update/delete) otherwise you'll end up having this behaviour.
Even if you do read-only operations in your methods, you should annotate them with #Transactional(readOnly=true) so Spring can optimize the transactional resource.
Can any one say to me that can I return Hibernate Entities as return value in JAXWS web service methods!?
Indeed I have some Entities like these:
#Entity
public class Parent {
...
private Childone childoneByChildoneid;
#ManyToOne
public
#javax.persistence.JoinColumn(name="ChildOneId",referencedColumnName="Id")
Childone getChildoneByChildoneid() {
return childoneByChildoneid;
}
public void setChildoneByChildoneid(Childone childoneByChildoneid) {
this.childoneByChildoneid = childoneByChildoneid;
}
...
}
#Entity
public class Childone {
...
private Collection<Parent> parentsById;
#OneToMany(mappedBy = "childoneByChildoneid")
public Collection<Parent> getParentsById() {
return parentsById;
}
public void setParentsById(Collection<Parent> parentsById) {
this.parentsById = parentsById;
}
...
}
And have a service like this:
#Stateless
#WebService()
public class MasterDataService {
#EJB
private MasterDataManager manager;
#WebMethod
public Parent getParent(int parentId) {
return manager.getParent(parentId);
}
}
#Stateless
public class MasterDataManager {
#PersistenceContext
EntityManager em;
public Parent getParent(int parentId) {
Parent parent = (Parent) em.createQuery(
"select p from Parent p where p.id=:parentId")
.setParameter("parentId", parentId).getSingleResult();
return parent;
}
}
When I call this web method from client I get LazyInitializationException exception :(
I test Serializable and Cloneable interfaces and override clone method but unfortunately it doesn't work, I use em.detach(parent) in manager but it doesn't work still.
Can any one help me?
tnax
It is debatable. Generally, you have two options:
return the entities, but make sure they are initialized. Either mark the #*ToMany with fetch=FetchType.EAGER or use Hibernate.initialize(..). The reason for the exception is that by default all collections in entities are not fetched from the database until requested. But when you request them from the jax-ws serializer, the hibernate session is already closed. Technically, you can have some OpenSessionInViewIntercepetor but I don't think there's something ready-to-use with JAX-WS, and it might be a problem to write one. If you don't want to transfer these collections, you can annotate them with #XmlTransient (or #JsonIgnore, depending on the serialization technique). It makes the entity somewhat of a mess, but I still prefer it to code duplication.
Use DTOs (data transfer objects) - transfer all data from the entity to a new object with a similar structure, that will be exposed by the web service. Again you'd have to make sure you are populating the DTO when the hibernate session is active
I prefer the first option, because it requires less biolerplate code, but I agree one should be very careful with entity state management when using it.