0

I am working in Spring Boot (2.1.7.RELEASE) + Spring Data JPA + postgres example and looking to rollback Primary Key Id in case of any exception occurs. I went through https://www.logicbig.com/tutorials/spring-framework/spring-data-access-with-jdbc/transactional-roll-back.html and How to rollback transaction in JPA? and many other useful links, but things did not worked for me.

In my project, I am always looking to store Unique Combination of firstName and LastName.

Student.java

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name="STUDENT", uniqueConstraints = {
        @UniqueConstraint(name="STU_KEY",columnNames = {"FIRSTNAME", "LASTNAME"})
})
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="STUDENT_ID")
    private Long studentId;

    @Column(name="FIRSTNAME")
    private String firstName;

    @Column(name="LASTNAME")
    private String lastName;

    @Column(name="EMAIL")
    private String email;
}

I have developed REST endpoint

StudentController.java

@RestController
public class StudentController {

    @Autowired
    private StudentService studentService;

    @ApiOperation(value = "Save Student", nickname = "Save Student")
    @ApiResponses(value = { @ApiResponse(code = 201, message = "Save Student Successful"),
            @ApiResponse(code = 500, message = "Internal Server Error"),
            @ApiResponse(code = 400, response = ErrorResource.class, message = "Program Type details are required ") })
    @PostMapping("/student")
    public ResponseEntity<HttpStatus> saveStudent(@ApiParam(value="Accepts a Student") @Valid @RequestBody StudentDto dto){
        studentService.saveStudent(dto);
        return new ResponseEntity<>(HttpStatus.CREATED);
    }
}

StudentServiceImpl.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

import com.example.demo.dto.StudentDto;
import com.example.demo.entity.Student;
import com.example.demo.exceptions.InternalServerException;
import com.example.demo.repository.StudentRepository;

import lombok.extern.slf4j.Slf4j;


@Service
@Slf4j
public class StudentServiceImpl implements StudentService {
    @Autowired
    private StudentRepository studentRepository;
    @Autowired
    private Environment e;

    @org.springframework.transaction.annotation.Transactional(rollbackFor= {DataIntegrityViolationException.class, Exception.class})
    @Override
    public void saveStudent(StudentDto dto) {
        Student studentEntity = convertToEntity(dto);
        try {
            studentRepository.save(studentEntity);
        } catch (DataIntegrityViolationException e) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "ConstraintViolationException", e.getCause());
        } catch (Exception ex) {
            log.error(e.getProperty("error.errors"), e.getProperty("DB Exception"));
            throw new InternalServerException(HttpStatus.INTERNAL_SERVER_ERROR, e.getProperty("DB Exception"), ex);
        }
    }

    private Student convertToEntity(StudentDto dto) {
        return Student.builder().firstName(dto.getFirstName()).lastName(dto.getLastName()).email(dto.getEmail())
                .build();
    }
}

Below is the request Payload: First Time, it will save successfully into DB. Next time, I hit the request with same payload. UniqueConstraints failed, again I hit the same.

{
  "email": "john.doe@gmail.com",
  "firstName": "John",
  "lastName": "Doe"
}

Now, this time I change payload to below and it saved into DB

{
  "email": "john1.doe1@gmail.com",
  "firstName": "John1",
  "lastName": "Doe1"
}

But I see Primary Key Sequence No: 2 and 3 has been consumed. Is there any way to reset Primary Key 2 and 3 and when request is successful, then I wanted to save record at Primary Key 2.

student_id |email                |firstname |lastname |
-----------|---------------------|----------|---------|
1          |john.doe@gmail.com   |John      |Doe      |
4          |john1.doe1@gmail.com |John1     |Doe1     |

Let me know if any other info is needed, even I can share my sample code too.

Pra_A
  • 7,134
  • 12
  • 89
  • 170
  • 2
    That's completely normal, expected and desirable. If the ID generator had to lock the sequence until the transaction is committed, all concurrent transactions would be blocked, waiting to get the next value, making the throughput horrible. An ID just needs to be unique. You shouldn't care about holes in the ID sequence. – JB Nizet Aug 27 '19 at 16:44
  • Agree and considering all these facts, still I am looking to achieve this somehow as in my application save() is not frequent operations and consumer very much caring about incremental sequence of Ids. Any workaround ? – Pra_A Aug 27 '19 at 16:46
  • Use a table with a row containing an integer. Every time you want an ID, lock the row (with `select for update` in SQL, or the equivalent with the JPA API), read its value, and increment it. The row will be unlocked at the end of the transaction. – JB Nizet Aug 27 '19 at 16:52
  • Could you please show some code ? Why `rollbackFor` is not taking effect ?? – Pra_A Aug 27 '19 at 16:54
  • 2
    Because incrementing an identity column is not part of the transaction, for the reason I explained in my first comment: it would be the best way to have craptastic performance, and deadlocks. And no, I can't show some example. Why don't you try doing it yourself? – JB Nizet Aug 27 '19 at 16:55
  • any solution? @Pra_A – Hem M Sep 24 '20 at 12:02

0 Answers0