H2 - duplicate key not throwing exception in Spring boot test when Persistable isNewObject set to true

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



H2 - duplicate key not throwing exception in Spring boot test when Persistable<ID> isNewObject set to true



I am using H2 as test DB in a Java Spring Boot application, but when I want to catch a "duplicate key" exception when trying to insert an duplicate ID/PK, H2 does not throw anything.



With Postman all is fine and nice, I just cannot pass the test.



The real DB is PostgreSQL, and it does throw exception when I integration test with Postman. But when unit testing I think it is not necessary to load real DB so I chose H2.



H2 config:


spring.datasource.url=jdbc:h2:mem:tesdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;mode=MySQL
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1

spring.jpa.datasource.show-sql=true
spring.h2.console.enabled=true # if you need console



Bean definition:


@Entity
@Data
@JsonComponent
@Table(name="bin_info")
public class BinInfo implements Serializable, Persistable<String>{ //with Persistable we can check ID duplicate
@Id
@Size(min=6, max=8)
@Column(name="bin")
@JsonProperty("bin")
private String bin;

...

/**
* Property for identifying whether the object is new or old,
* will insert(new) or update(old)
* If is new and id/bin is duplicate, org.hibernate.exception.ConstraintViolationException will be thrown.
* If is old and id/bin is duplicate, just updates. Hibernate save() will upsert and no complain.
*/
@Transient
private boolean isNewObject;

@Override
public String getId()
return this.bin;


@Override
public boolean isNew()
return isNewObject;

@Override
public String getId()
return this.bin;


@Override
public boolean isNew()
return isNewObject;



The controller insert method:


insert


@RequestMapping(value="/insert", method=RequestMethod.POST)
public ResponseEntity<Object> insertBIN(@Valid @RequestBody BinInfo bin_info, HttpServletResponse response) throws JsonProcessingException {
Map<String, Object> errors = new HashMap<String, Object>();
try
OffsetDateTime now = OffsetDateTime.now();
bin_info.setCreatedAt(now);
bin_info.setUpdatedAt(now);
bin_info.setNewObject(true); //if set to true, bin duplicate -> exception and return 200; then we avoid "select to check duplicate first"
BinInfo saved = repository.save(bin_info);
// if is new, created(201); if not, updated(status OK, 200)
return ResponseEntity.status(HttpStatus.CREATED)
.contentType(MediaType.APPLICATION_JSON_UTF8).body(saved);
catch (DataIntegrityViolationException e0)
log.warn("Update BIN due to duplicate", e0); // exception details in log
//if duplicate, change newObject to false and save again. And return.
bin_info.setNewObject(false);
BinInfo saved = repository.save(bin_info);
return ResponseEntity.ok() // <<<<<< here I define "save duplicate=update=200, OK"
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(saved);
catch (Exception e)
log.error("Cannot save BinInfo. ", e); // exception details in log
errors.put("error", "Cannot save BIN"); // don't expose to user
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(Utilities.jsonBuilder(errors));



The test:


@Test
public void testBinInfoControllerInsertBIN() throws Exception
when(this.repository.save(any(BinInfo.class))).thenReturn(mockBinInfo);
String content_double_quotes = ""bin":"123456", "
+ ""json_full":"" + this.json_full + "", "
+ ""brand":"" + this.brand + "", "
+ ""type":"" + this.type + "", "
+ ""country":"" + this.country + "", "
+ ""issuer":"" + this.issuer + "", "
+ ""newObject":true, "
+ ""new":true, "
+ ""createdAt":"18/08/2018 02:00:00 +0200", "
+ ""updatedAt":"18/08/2018 02:00:00 +0200"";
log.info("JSON input: " + content_double_quotes);

//save the entity for the first time(newObject==true, new=true) and should return 201
this.mockMvc.perform(post("/insert")
.content(content_double_quotes) //json cannot have single quote; must be double
.accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
)
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(status().isCreated())
.andDo(print())
.andExpect(jsonPath("$.bin", is(this.bin)))
.andExpect(jsonPath("$.json_full", is(this.json_full)))
.andExpect(jsonPath("$.brand", is(this.brand)))
.andExpect(jsonPath("$.type", is(this.type)))
.andExpect(jsonPath("$.country", is(this.country)))
.andExpect(jsonPath("$.issuer", is(this.issuer)))
.andExpect(jsonPath("$.createdAt", is("18/08/2018 02:00:00 +0200")))
.andExpect(jsonPath("$.updatedAt", is("18/08/2018 02:00:00 +0200")));

//save the same entity, new == true, and should return 200
this.mockMvc.perform(post("/insert")
.content(content_double_quotes) //json cannot have single quote; must be double
.accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andDo(print())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(status().isOk()); //<<<<< here I always get 201, not 200. With Postman I get 200 instead.



Note that mockBinInfo has isNewObject always set to true, meaning that it should throw exception when finding duplicate PK in the second insertion, but it is not happening. This is required by Persistable<ID> interface, which will tell DB whether or not to persist when ID is duplicate or not.


mockBinInfo


isNewObject


true


Persistable<ID>


isNew()


true



See here for more information(searching "Persistable").



EDIT:



I also noticed that H2 seems not to support Spring Persistable<ID> and always return new=false in the saved entity.


Persistable<ID>


new=false



Log details:


MockHttpServletRequest:
HTTP Method = POST
Request URI = /insert
Parameters =
Headers = Content-Type=[application/json;charset=UTF-8], Accept=[application/json;charset=UTF-8]
Body = "bin":"123456", "json_full":"'brand':'visa', 'type':'credit', 'country':'USA', 'issuer':'BigBank'", "brand":"visa", "type":"credit", "country":"USA", "issuer":"BigBank", "newObject":"true", "new":"true", "createdAt":"18/08/2018 02:00:00 +0200", "updatedAt":"18/08/2018 02:00:00 +0200"
Session Attrs =

Handler:
Type = com.xxxxx.binlookup.controller.BinInfoController
Method = public org.springframework.http.ResponseEntity<java.lang.Object> com.xxxxx.binlookup.controller.BinInfoController.insertBIN(com.xxxxx.binlookup.model.BinInfo,javax.servlet.http.HttpServletResponse) throws com.fasterxml.jackson.core.JsonProcessingException

Async:
Async started = false
Async result = null

Resolved Exception:
Type = null

ModelAndView:
View name = null
View = null
Model = null

FlashMap:
Attributes = null

MockHttpServletResponse:
Status = 201
Error message = null
Headers = Content-Type=[application/json;charset=UTF-8]
Content type = application/json;charset=UTF-8
Body = "id":"123456","newObject":false,"new":false,"bin":"123456","json_full":"'brand':'visa', 'type':'credit', 'country':'USA', 'issuer':'BigBank'","brand":"visa","type":"credit","country":"USA","issuer":"BigBank","createdAt":"18/08/2018 02:00:00 +0200","updatedAt":"18/08/2018 02:00:00 +0200"
Forwarded URL = null
Redirected URL = null
Cookies =




1 Answer
1



If you call save() on an existing entity, Hibernate will not persist it again, but will just update the entity.


save()





Yes but maybe I was not clear: Persistable enables you to mark the object as new or old, thus adopting different strategies when saving. See edits of my question.
– WesternGun
Aug 13 at 9:53


Persistable






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

make 2 or more post in bootsrap

Store custom data using WC_Cart add_to_cart() method in Woocommerce 3

Firebase Auth - with Email and Password - Check user already registered