Spring JPA CrudRepository save(Entity) is returning 0 in id field - spring

I am adding spring JPA/Hibernate 5 to an old project. I am running CrudRepository .save(Entity) method against a Mainframe/DB2 table. The row is inserted just fine, but returnedEntity.getIdColumn() has 0 in it. I really need the ID for further processing. Could somebody please help? Thank you very much!
#Entity
Table(name="MY_TABLE")
public class myClass {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="ID_COLUMN")
private Long idColumn;
...
}
Identity is the only strategy type that worked.
Here is the Service class:
#Transactional
public Entiry insertEntity(Entity originalEntity) {
return MyRepository.save(originalEntity);
}
Runner class:
Entity originalEntity = createEntity();
Entity returnedEntity = ServiceClass.insertEntity(originalEntity);
System.out.println(originalEntity.getIdColumn());
System.out.println(returnedEntity.getIdColumn());

My guess is that you're trying to get an ID before your transaction was flushed to DB. Thus JPA doesn't know what id will be assigned and returns 0.
Edited:
I'd recommend to have something like this:
#Transactional
public Entity save( .....) {
//some code
repository.save(entity);
//some code
return entity;
}
Transaction will be flushed at the end of this method and entity which will be returned from it should have a real id.

Turned out my table had an id sequence generator already defined in the DB. So, after I changed it to generationType.SEQUENCE it worked nicely.
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MY_TABLE_SEQ")

Recently, I've faced with similar issue.
In my case, the new entity has been passed from UI and ID was 0 instead of NULL (since the type of ID was primitive on Java side). Therefore Hibernate didn't use right save strategy.
Eventually, I changed the type from long to Long and it helped:
private long id;
private Long id;
Also update method was changed from:
#PutMapping("/{id}")
public T update(#PathVariable ID id, #Valid #RequestBody T entity) {
this.getService().update(entity);
return entity;
}
to:
#PutMapping("/{id}")
public T update(#PathVariable ID id, #Valid #RequestBody T entity) {
return this.getService().update(entity);
}
P.S.: When entity ID was 0, Hibernate was using merge strategy, however, it should persist strategy for new Entities instead (link for implementation of save method).

Related

How to do findById for autogenerated id

I have a scenario where I am consuming an event and saving the details in the DB. Now the record that is being stored in the database has the id field autogenerated #GeneratedValue(strategy = GenerationType.IDENTITY).
In my test case I need to check if data is getting stored in the DB or not and is as per expectation.
But I am not sure how will I do findById() of SpringBoot Crud/JPA Repository since I do not know what value got generated.
Any help would be appreciated.
Take a look at save method from CrudRepository interface. Spring executes this method in transaction and after its completion Hibernate will generate identifier in returned entity.
Suppose your entity and repository looks as following:
....
public class SomeEntity {
#Id
#GeneratedValue
private Long id;
private String name;
public SomeEntity(String name){
this.name = name;
}
....
}
public interface SomeRepository extends CrudRepository<SomeEntity, Long> {
}
After saving entity:
SomeEntity someEntity = someRepository.save(new SomeEntity("Some entity"));
someEntity.getId() will contain actual record id which can be used further in your tests.
I think you are looking for annotation #DirtiesContext .
It is a Test annotation which indicates that the ApplicationContext associated with a test is dirty and should therefore be closed and removed from the context cache. - javadoc
Read Section 9.3.4 - Here
Check - Example ans below as well:
#Test
#DirtiesContext
public void save_basic() {
// get a course
Course course = courseJpaRepository.findById(10001L);
assertEquals("JPA ", course.getName());
// update details
course.setName("JPA - Updated");
courseJpaRepository.saveOrUpdate(course);
// check the value
Course course1 = courseJpaRepository.findById(10001L);
assertEquals("JPA - Updated", course1.getName());
}
BTW - how you can get the id : simply via getter method from the return type of save
EmployeeDetails employeeDetails = emaployeeService.saveEmployeeDetails(employee);
int temp = employeeDetails.getID()
Related Post : Here

Unable to initialize lazy-loaded relationship inside of `#Transactional` method

I have a set of simple models like this (getters and setters omitted for brevity):
#Entity
public class Customer {
#Id
private Integer id;
}
#Entity
public class Order {
#Id
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer_id")
private Customer customer;
}
I am trying to load an Order using a Spring JPA repository with a findById method, including the customer.
First I tried this:
#Transactional
Optional<Order> findById(Integer id) {
return repository.findById(id);
}
But when I tried to access Customer I got a LazyInitializationException: could not initialize proxy - no Session. So after referring to some other questions, I updated my method to be a bit uglier, but to explicitly call Hibernate.initialize:
#Transactional
Optional<Order> findById(Integer id) {
return repository.findById(id)
.map( order -> {
Hibernate.initialize(order.getCustomer());
return order;
);
}
But I still get org.hibernate.LazyInitializationException: could not initialize proxy - no Session. repository is a regular CrudRepository which provides the findById method out-of-the-box.
How can I initialize this lazily loaded child entity? My understanding is that the #Transactional indicates that I should still be within the transaction for the entirety of this method call. The only thing further downstream is the repository itself, which is just an interface, so I'm not sure how else to go about forcing the load of this child entity.
The Order entity and everything else in it is retrieved properly from the database; it's only when I try to get the lazy-loaded child entities that we start having issues.
The only way I managed to get this working was to write a custom hql method in the Repository using a left join fetch. While that works, it clutters up my repository with a method that is pretty much a duplicate of another and which I'm pretty sure I'm not actually supposed to need (so I would rather not do it this way.)
Spring-Boot 2.1.4.RELEASE, Spring 5.1.6.RELEASE, Hibernate 5.3.7.Final.
You have to define the method as public. See "Method visibility and #Transactional" in the spring docs.
This should work:
#Transactional
public Optional<Order> findById(Integer id) {
Optional<Order> order = repository.findById(id);
order.ifPresent(o -> Hibernate.initialize(o.getCustomer()));
return order;
}

How to write a RestController to update a JPA entity from an XML request, the Spring Data JPA way?

I have a database with one table named person:
id | first_name | last_name | date_of_birth
----|------------|-----------|---------------
1 | Tin | Tin | 2000-10-10
There's a JPA entity named Person that maps to this table:
#Entity
#XmlRootElement(name = "person")
#XmlAccessorType(NONE)
public class Person {
#Id
#GeneratedValue
private Long id;
#XmlAttribute(name = "id")
private Long externalId;
#XmlAttribute(name = "first-name")
private String firstName;
#XmlAttribute(name = "last-name")
private String lastName;
#XmlAttribute(name = "dob")
private String dateOfBirth;
// setters and getters
}
The entity is also annotated with JAXB annotations to allow XML payload in
HTTP requests to be mapped to instances of the entity.
I want to implement an endpoint for retrieving and updating an entity with a given id.
According to this answer to a similar question,
all I need to do is to implement the handler method as follows:
#RestController
#RequestMapping(
path = "/persons",
consumes = APPLICATION_XML_VALUE,
produces = APPLICATION_XML_VALUE
)
public class PersonController {
private final PersonRepository personRepository;
#Autowired
public PersonController(final PersonRepository personRepository) {
this.personRepository = personRepository;
}
#PutMapping(value = "/{person}")
public Person savePerson(#ModelAttribute Person person) {
return personRepository.save(person);
}
}
However this is not working as expected as can be verified by the following failing test case:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
public class PersonControllerTest {
#Autowired
private TestRestTemplate restTemplate;
private HttpHeaders headers;
#Before
public void before() {
headers = new HttpHeaders();
headers.setContentType(APPLICATION_XML);
}
// Test fails
#Test
#DirtiesContext
public void testSavePerson() {
final HttpEntity<Object> request = new HttpEntity<>("<person first-name=\"Tin Tin\" last-name=\"Herge\" dob=\"1907-05-22\"></person>", headers);
final ResponseEntity<Person> response = restTemplate.exchange("/persons/1", PUT, request, Person.class, "1");
assertThat(response.getStatusCode(), equalTo(OK));
final Person body = response.getBody();
assertThat(body.getFirstName(), equalTo("Tin Tin")); // Fails
assertThat(body.getLastName(), equalTo("Herge"));
assertThat(body.getDateOfBirth(), equalTo("1907-05-22"));
}
}
The first assertion fails with:
java.lang.AssertionError:
Expected: "Tin Tin"
but: was "Tin"
Expected :Tin Tin
Actual :Tin
In other words:
No server-side exceptions occur (status code is 200)
Spring successfully loads the Person instance with id=1
But its properties do not get updated
Any ideas what am I missing here?
Note 1
The solution provided here is not working.
Note 2
Full working code that demonstrates the problem is provided
here.
More Details
Expected behavior:
Load the Person instance with id=1
Populate the properties of the loaded person entity with the XML payload using Jaxb2RootElementHttpMessageConverter or MappingJackson2XmlHttpMessageConverter
Hand it to the controller's action handler as its person argument
Actual behavior:
The Person instance with id=1 is loaded
The instance's properties are not updated to match the XML in the request payload
Properties of the person instance handed to the controller's action handler method are not updated
this '#PutMapping(value = "/{person}")' brings some magic, because {person} in your case is just '1', but it happens to load it from database and put to ModelAttribute in controller. Whatever you change in test ( it can be even empty) spring will load person from database ( effectively ignoring your input ), you can stop with debugger at the very first line of controller to verify it.
You can work with it this way:
#PutMapping(value = "/{id}")
public Person savePerson(#RequestBody Person person, #PathVariable("id") Long id ) {
Person found = personRepository.findOne(id);
//merge 'found' from database with send person, or just send it with id
//Person merged..
return personRepository.save(merged);
}
wrong mapping in controller
to update entity you need to get it in persisted (managed) state first, then copy desired state on it.
consider introducing DTO for your bussiness objects, as, later, responding with persisted state entities could cause troubles (e.g. undesired lazy collections fetching or entities relations serialization to XML, JSON could cause stackoverflow due to infinite method calls)
Below is simple case of fixing your test:
#PutMapping(value = "/{id}")
public Person savePerson(#PathVariable Long id, #RequestBody Person person) {
Person persisted = personRepository.findOne(id);
if (persisted != null) {
persisted.setFirstName(person.getFirstName());
persisted.setLastName(person.getLastName());
persisted.setDateOfBirth(person.getDateOfBirth());
return persisted;
} else {
return personRepository.save(person);
}
}
Update
#PutMapping(value = "/{person}")
public Person savePerson(#ModelAttribute Person person, #RequestBody Person req) {
person.setFirstName(req.getFirstName());
person.setLastName(req.getLastName());
person.setDateOfBirth(req.getDateOfBirth());
return person;
}
The issue is that when you call personRepository.save(person) your person entity does not have the primary key field(id) and so the database ends up having two records with the new records primary key being generated by the db. The fix will be to create a setter for your id field and use it to set the entity's id before saving it:
#PutMapping(value = "/{id}")
public Person savePerson(#RequestBody Person person, #PathVariable("id") Long id) {
person.setId(id);
return personRepository.save(person);
}
Also, like has been suggested by #freakman you should use #RequestBody to capture the raw json/xml and transform it to a domain model. Also, if you don't want to create a setter for your primary key field, another option may be to support an update operation based on any other unique field (like externalId) and call that instead.
For updating any entity the load and save must be in same Transaction,else it will create new one on save() call,or will throw duplicate primary key constraint violation Exception.
To update any we need to put entity ,load()/find() and save() in same transaction, or write JPQL UPDATE query in #Repository class,and annotate that method with #Modifying .
#Modifying annotation will not fire additional select query to load entity object to update it,rather presumes that there must be a record in DB with input pk,which needs to update.

JPA Hibernate Spring Repository ensures transaction completes on save?

I am creating a simple spring application which is supposed to book seats in a seminar. Lets say Booking class looks like this
#Entity
#Table(name = "bookings")
#IdClass(BookingId.class)
public class Booking{
#Id
private Long seminarId;
#Id
private String seatNo;
// .. other fields like perticipant info
// .. getter setters
}
of course the BookingId class:
public class BookingId implements Serializable{
private static final long serialVersionUID = 1L;
private Long seminarId;
private String seatNo;
// .. constructors, getters, setters
}
And I have a repository
#Repository
public interface BookingsRepository extends JpaRepository<Booking, BookingId>{
}
in the controller when a booking request arrives I first check if a booking with same seminer id and seat number already exists, if it doesn't exist I create one
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<BaseCrudResponse> createNewBooking(#Valid #RequestBody NewBookingDao newBookingDao, BindingResult bindingResult){
logger.debug("Request for a new booking");
// .. some other stuffs
Booking newBooking = new Booking();
newBooking.setSeminarId(newBookingDao.getSeminarId());
newBooking.setSeatNumber(newBookingDao.getSeatNumber());
// .. set other fields
Booking existing = bookingsRepository.findOne(new BookingId(newBooking.getSeminarId(), newBooking.getSeatNumber());
if (existing == null)
bookingsRepository.save(newBooking);
return new ResponseEntity<>(new BaseCrudResponse(0), HttpStatus.CREATED);
}
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
Now what will happen if the save method of the repository didn't finish commiting transaction and another request already gets past the existence check ? There might be incorrect booking (the last commit will override the previous). Is this scenario likely to happen ? Will the repository ensures that it completes the transaction before another save call ?
Also is there any way to tell Jpa to throw some exception (for IntegrityConstraintException if the composite key (in this case seminerId and seatNumber) already exists ? Now in the present setting its just updating the row.
You can use javax.persistence.LockModeType.PESSIMISTIC_WRITE so other transactions except the one that got the lock cannot update the entity.
If you use spring-data > 1.6 you can annotate the repository method with #Lock :
interface BookingsRepository extends Repository<Booking, Long> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
Booking findOne(Long id);
}
For sure you need to handle the locking exception that may be thron in the controller.

I need help for persisting into oracle database

There is a problem about generating id while persisting into database.
I added the following code to my jpa entity file, however I'm getting 0 for personid.
#Id
#Column(unique=true, nullable=false, precision=10, name="PERSONID")
#SequenceGenerator(name="appUsersSeq", sequenceName="SEQ_PERSON", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "appUsersSeq")
private long personid;
EjbService:
#Stateless
public class EjbService implements EjbServiceRemote {
#PersistenceContext(name = "Project1245")
private EntityManager em;
#Override
public void addTperson(Tperson tp) {
em.persist(tp);
}
}
0 is default value for long type. The id will be set after invoking select query for the related sequence, which commonly is executed when you persist the entity. Are you persisting the entity? In case yes, post the database sequence definition to check it.

Resources