How to determine violated entity during batch saving in spring data - java

I have next problem
public void batchSave(List<Entity> entities) {
repositoryJpa.save(entities);
}
If entities list contains already persisted entity i got DataIntegrityViolationException.
Is there way how to know which entity violated persisting?
Researching spring data source code and DataIntegrityViolationException i could find any place where wrong entity can be stored.
UPD
public class Entity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "GENERATOR")
#Column(name = "ID", unique = true, nullable = false)
public Long getId() {
return id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "NTRY_TABS_TABH_ID", nullable = false)
public OtherEntity getOtherEntity() {
return otherEntity;
}
#Column(name = "SORT_KEY", nullable = false)
public String getSortKey() {
return sortKey;
}
#Enumerated(EnumType.STRING)
#Column(name = "TYPE", nullable = false)
public OperationType getOperationType() {
return operationType;
}
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "activeEntryEntity")
public SortKeyEntity getSortKeyEntity() {
return sortKeyEntity;
}
#Version
#Column(name = "VERSION", nullable = false, insertable = false)
public Long getVersion() {
return version;
}
}

Use javax validation , and you will be able to do something like this:
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.constraints.Email;
public class User {
#NotNull(message = "Name cannot be null")
private String name;
#AssertTrue
private boolean working;
#Size(min = 10, max = 200, message
= "About Me must be between 10 and 200 characters")
private String aboutMe;
#Min(value = 18, message = "Age should not be less than 18")
#Max(value = 150, message = "Age should not be greater than 150")
private int age;
#Email(message = "Email should be valid")
private String email;
// standard setters and getters
}
And then you can validate this like :
Set<ConstraintViolation<User>> violations = validator.validate(user);
Take a look at this:
https://www.baeldung.com/javax-validation

Related

How to solve problem when Java Bean mapper has different source and dest field in payload?

I have Company entity:
#Entity
#Data
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "company_id", nullable = false, unique = true)
private int companyId;
#Column(name = "name", nullable = false, length = 255, unique = true)
private String name;
#JsonIgnore
#OneToMany (mappedBy = "company")
private List<Employee> employeeList;
}
And Employee entity:
#Entity
#Data
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "employee_id", nullable = false, unique = true)
private int employeeId;
#Column(name = "employee_name", nullable = false)
private String name;
#ManyToOne(optional = false)
#JoinColumn(name = "company_id", nullable = false)
private Company company;
}
I have trouble when I'm mapping EmployeeRequest to Employee object.
This is EmployeeRequest:
#Data
public class EmployeeRequest {
private String name;
private int companyId;
}
As you can see here in request I have companyId which is integer and therefore I have error.
My mapper:
#Component
public class EmployeeMapper {
private Mapper mapper = DozerBeanMapperBuilder.buildDefault();
public Employee transformToEmployeeEntity(EmployeeRequest employeeRequest) {
return mapper.map(employeeRequest, Employee.class);
}
}
I think it makes sense to have an integer as data type in EmployeeRequest. So what is proper way to solve this?

"Provided id of the wrong type for" error when using save() method of an #Embeddable class

I am still working on my very first solo spring boot project. It is suppose to be a Rest API using the MariaDB example database Nation. There is the country_languages table which receives two foreign keys that also are the primary keys and has another regular field. First foreign key is the id from countries table and the second one is the id from languages table. When I use the save() method in order to create a new tuple I get this error:
org.springframework.dao.InvalidDataAccessApiUsageException: Provided id of the wrong type for class me.givo.nationdbapiproject.model.CountryLanguages. Expected: class me.givo.nationdbapiproject.model.CountryLanguagesId, got class java.lang.Integer; nested exception is java.lang.IllegalArgumentException: Provided id of the wrong type for class me.givo.nationdbapiproject.model.CountryLanguages. Expected: class me.givo.nationdbapiproject.model.CountryLanguagesId, got class java.lang.Integer
This is the country_languages table from the MariaDB example:
create table country_languages(
country_id int,
language_id int,
official boolean not null,
primary key (country_id, language_id),
foreign key(country_id)
references countries(country_id),
foreign key(language_id)
references languages(language_id)
);
I am using an #Embeddable class CountryLanguagesId in order to make a composite key as I found in this reference.
#Embeddable
public class CountryLanguagesId implements Serializable {
#Column(name = "country_id")
private Integer countryId;
#Column(name = "language_id")
private Integer languageId;
public CountryLanguagesId() {
}
public CountryLanguagesId(Integer countryId, Integer languageId) {
this.countryId = countryId;
this.languageId = languageId;
}
// + getters and setters
After that I created the entity for the country_languages table and its repository:
#Entity
#Table(name = "country_languages")
public class CountryLanguages {
#EmbeddedId
CountryLanguagesId countryLanguagesId = new CountryLanguagesId();
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#MapsId("countryId")
#JoinColumn(name = "country_id")
private Countries countries;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#MapsId("languageId")
#JoinColumn(name = "language_id")
private Languages languages;
#Column(name = "official", length = 1, nullable = false)
private Integer official;
public CountryLanguages() {
}
public CountryLanguages(Countries country, Languages language, Integer official) {
this.countries = country;
this.languages = language;
this.official = official;
}
// + getters and setters
#Repository
public interface ICountryLanguagesJpaRepository extends JpaRepository<CountryLanguages, Integer> {
}
There are the countries and languages entities:
#Entity
#Table(name = "countries")
public class Countries {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "country_id", length = 11, nullable = false)
private Integer countryId;
#Column(name = "name", length = 50, nullable = true)
private String name;
#Column(name = "area", nullable = false)
private BigDecimal area;
#Column(name = "national_day", nullable = true)
private java.sql.Date nationalDay;
#Column(name = "country_code2", length = 2, nullable = false)
private String countryCode2;
#Column(name = "country_code3", length = 3, nullable = false)
private String countryCode3;
#OneToMany(mappedBy = "countries", cascade = CascadeType.ALL)
private Set<CountryLanguages> countryLanguages;
public Countries() {
}
public Countries(String name, BigDecimal area, Date nationalDay, String countryCode2, String countryCode3) {
this.name = name;
this.area = area;
this.nationalDay = nationalDay;
this.countryCode2 = countryCode2;
this.countryCode3 = countryCode3;
}
#Entity
#Table(name = "languages")
public class Languages {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "language_id", length = 11, nullable = false)
private Integer languageId;
#Column(name = "language", length = 50, nullable = false)
private String language;
#OneToMany(mappedBy = "languages", cascade = CascadeType.ALL)
private Set<CountryLanguages> countryLanguages;
public Languages() {
}
public Languages(String language) {
this.language = language;
}
public Integer getLanguageId() {
return languageId;
}
These are the entries I do when get the error:
#DataJpaTest
#AutoConfigureTestDatabase(replace = Replace.NONE)
public class ICountryLanguagesJpaRepositoryTest {
#Autowired
private ICountriesJpaRepository countries;
#Autowired
private ILanguagesJpaRepository languages;
#Autowired
private ICountryLanguagesJpaRepository repository;
#Test
public void shouldSaveAndRemoveContinents() {
Countries patu = new Countries("Patu", new BigDecimal(67822.34), new Date(12321233232L), "PU", "PTU");
countries.save(patu);
Languages patuano = new Languages("Patuano");
languages.save(patuano);
CountryLanguages pLanguages = new CountryLanguages(patu, patuano, 0);
repository.save(pLanguages);
assertEquals(1, repository.findAll().size());
System.out.println(repository.findAll());
repository.deleteById(1);
assertEquals(0, repository.findAll().size());
}
I am doing this using a H2 database. Here is the complete debug console output. Sorry but cant paste it here due characters limitation.
Thanks!
Your repository definition is wrong. You should specify the embeddable type as primary key type:
#Repository
public interface ICountryLanguagesJpaRepository extends JpaRepository<CountryLanguages, CountryLanguagesId> {
}

hibernate OneToOne with EmbeddedId

The "TypeMismatchException: Provided id of the wrong type" error thrown when tried to merge detached entity. It works if the object wasn't detached. It also works if ids aren't #EmbeddedId.
A sample repo can be found here https://github.com/joes-code/hibernate-map
// Asset.java
#Entity
#Table(name = "asset")
public class Asset {
#EmbeddedId
private AssetId id;
#Column(name = "asset_cost"
private BigDecimal price;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "asset_id", referencedColumnName = "asset_id", nullable = false, insertable = false, updatable = false, foreignKey = #ForeignKey(ConstraintMode.NO_CONSTRAINT))
private AssetDetail assetDetail;
}
// AssetId.java
#Embeddable
public class AssetId {
#Column(name = "asset_id", nullable = false)
private Integer assetId;
}
// AssetDetail.java
#Entity
#Table(name = "asset_detail")
public class AssetDetail {
#EmbeddedId
private AssetDetailId id;
#Column(name = "description", length = 35)
private String description;
}
// AssetDetailId.java
#Embeddable
public class AssetDetailId {
#Column(name = "asset_id", nullable = false)
private Integer assetId;
}
I'm using Hibernate 5.4.3.Final
Any ideas what I did wrong? It seems that Hibernate is assuming Asset and AssetDetail share the same Id class?

How to iterate List<Tuple> in java

I am trying to write JUnit test case for controller class. Below Test case is running properly without any failure. But In that Test case I am checking expected result using Json path. I want to check expected String result with actual String result using AssertEquals() method. For that I want to know
How to iterate List<Tuple> result? I want to iterate this queryResult :
List<Tuple> queryResult = new ArrayList<>();
AccountController class
#GetMapping("/findAccountData")
public ResponseEntity<List<Tuple>> populateGridViews(#RequestParam(value="sClientAcctId",required=false) String sClientAcctId,
#RequestParam(value="sAcctDesc",required=false) String sAcctDesc,
#RequestParam(value="sInvestigatorName",required=false)String sInvestigatorName,
#RequestParam(value="sClientDeptId",required=false) String sClientDeptId) throws Exception {
return ResponseEntity.ok(accService.populateGridViews(sClientAcctId, sAcctDesc,sInvestigatorName,sClientDeptId));
}
Junit Test Case:
#Test
#Transactional
public void populateGridViewsTest() throws Exception {
String sClientAcctId = "5400343";
String sAcctDesc = " ASTRALIS LTD";
String sInvestigatorName = "Krueger, James G.";
String sClientDeptId = "112610";
QAccount account = QAccount.account;
JPAQuery<Tuple> query = new JPAQuery<Tuple>(em);
List<Tuple> result = query.from(account).fetch();
Mockito.when(accountService.populateGridViews(sClientAcctId, sAcctDesc, sInvestigatorName, sClientDeptId))
.thenReturn(result);
mockMvc.perform(get("/spacestudy/$ InstituteIdentifier/admin/account/findAccountData")
.param("sClientAcctId", "5400343")
.param("sAcctDesc", " ASTRALIS LTD")
.param("sInvestigatorName", "Krueger, James G.")
.param("sClientDeptId", "112610")
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
.andExpect(jsonPath("$[0].sAcctDesc", is(" ASTRALIS LTD")))
.andExpect(jsonPath("$[0].sClientAcctId", is("5400343")))
.andExpect(jsonPath("$[0].sLocation", is("A")))
.andExpect(jsonPath("$[0].investigator.sInvestigatorName", is("Krueger, James G.")))
.andDo(print());
}
}
Account.java
#Entity
#Table(name = "account")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "account_seq_generator")
#SequenceGenerator(name = "account_seq_generator", sequenceName = "account_seq")
#Column(name = "naccount_id")
public Integer nAccountId;
#Column(name = "namount")
public String nAmount;
#Column(name = "sacct_desc")
public String sAcctDesc;
#Column(name = "naccount_cpc_mapping_id")
public Integer nAccountCPCMappingId;
#Column(name = "nindirect_cost_rate")
public Integer nIndiretCostRate;
#Column(name = "nagency_id")
public Integer nAgencyId;
#Column(name = "ndept_id")
public Integer nDeptId;
#Column(name = "sgrant_num")
public String sGrantNum;
#Column(name = "dstart_date")
public Timestamp dStartDate;
#Column(name = "dend_date")
public Timestamp dEndDate;
#Column(name = "slocation")
public String sLocation;
#Column(name = "sclient_acct_id")
public String sClientAcctId;
#Column(name = "ninvestigator_id")
public Integer nInvestigatorId;
#Column(name = "ninst_id")
public Integer nInstId;
#Column(name = "ntemp_account_id")
public Integer nTempAccountId;
#ManyToOne(optional = true, cascade = { CascadeType.MERGE })
#JoinColumn(name = "ndept_id", insertable = false, updatable = false)
public Department department;
#ManyToOne(optional = true, cascade = { CascadeType.ALL })
#JoinColumn(name = "ninvestigator_id", insertable = false, updatable = false)
public Investigator investigator;
#ManyToOne(optional = true, cascade = { CascadeType.ALL })
#JoinColumn(name = "naccount_cpc_mapping_id", insertable = false, updatable = false)
public AccountCPCMapping accountCPC;
to iterate list use for example for each:
for (Tuple tuple : queryResult){
'put code and assertion here'
}

Customize form bean creation process in spring

I have the following bean:
public class TerminalAdmin {
#Id
#Column(name = "admin_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long adminId;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "phone")
#Size(max = 255)
private String phone;
#Size(max = 255)
#Column(name = "name")
private String name;
#Column(name = "registration_date")
#Temporal(TemporalType.TIMESTAMP)
private Calendar createDate;
#Column(name = "password", nullable = false)
#Size(min=1, max = 255, message = "введите пароль длиной от 1 до 255 символов")
private String password;
#ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
#JoinTable(name = "admin_role", joinColumns = {
#JoinColumn(name = "admin_id", nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
nullable = false) })
private Set<AdminRole> adminRoles;
#Column(name = "blocked")
private boolean blocked;
...
}
and this:
public class AdminRole {
#Id
#Column(name = "role_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long id;
#Column(name = "role")
private String role;
....
}
Inside controller:
#RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(#Valid TerminalAdmin terminalAdmin,
BindingResult bindingResult, ModelMap model, Principal principal, HttpSession session) {
from client side I send following request:
terminalAdmin comes to the method looks like this
Why spring writes values into role field?
How to force spring write 250/251 into id field?
P.S.
I tried to write
InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(AdminRole.class, new PropertyEditorSupport() {
public void setAsText(String name) {
....
}
});
}
but setAsText method doesn't invoke.
This is not a good practice to populate model objects into to forms since Spring can bind fields to object even if they are not populated into the view if your init binder is not properly configured.
Easiest way is to create DTO objects, eg. you could create AdminTerminalDTO or AdminTerminalForm wich you populate to the view.
The Form could contain same fields as AdminTerminal excluding ID field or any other sensitive fields. You cant insert new ID's from the view since it can cause DB integrity errors.
After successful validation you just persist your model object filling it with DTO/Form Object.
Moreover your JSR-303 Annotations seem to be not used in a proper way.
The #Size Annotation is not proper a validation to check String length. You have to use #Length instead. You use #Size to check length of an arrays. #Size also works on Strings but #Length is more accurate.
You can't just send an Integer and just try to bind to your Set(spring does some weird binding as you can see now) . Instead you already done addNewAdmin method in your controller wich already informs that it adds an Admin User.
You have to assign admin role on the server side right in this method. First you can use DTO wich will contain eg. username,password and other fields. You annote them with proper JSR-303 Annotations. Using bindingResult you check if there were any validation errors. If form is validated fine, you just convert your DTO/Form object to Model object. Then you can add admin role and persist your model object.
I can write some example code if this tips are not enough.
EDIT:
public class TerminalAdminDTO {
private String username;
#Length(max = 255)
public String getUsername(){
return username;
}
public void setUsername(String username){
this.username = username;
}
public TerminalAdmin convertToTerminalAdmin(){
TerminalAdmin terminalAdmin = new TerminalAdmin();
terminalAdmin.setUsername(this.username);
return terminAdmin;
}
}
#Entity
#Table
public class TerminalAdmin {
#Id
#Column(name = "admin_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long adminId;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "phone")
#Size(max = 255)
private String phone;
#Size(max = 255)
#Column(name = "name")
private String name;
#Column(name = "registration_date")
#Temporal(TemporalType.TIMESTAMP)
private Calendar createDate;
#Column(name = "password", nullable = false)
#Size(min=1, max = 255, message = "введите пароль длиной от 1 до 255 символов")
private String password;
#ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
#JoinTable(name = "admin_role", joinColumns = {
#JoinColumn(name = "admin_id", nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
nullable = false) })
private Set<AdminRole> adminRoles;
#Column(name = "blocked")
private boolean blocked;
...
}
#RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(#Valid TerminalAdminDTO terminalAdminDTO,
BindingResult bindingResult, ModelMap model, Principal principal, HttpSession session) {
if(result.hasErrors()){
return "errorPage";
}else{
userService.createAdminUser(terminalAdminDTO);
return "successPage";
}
}
#Service
#Transactional
public class UserServiceImpl implements UserService {
private final int ADMIN_ROLE_ID = 0;
#Autowired
EntityManager entityManager;
public void createAdminUser(TerminalAdminDTO terminalAdminDTO){
TerminalAdmin terminalAdmin = terminalAdminDTO.convertToTerminalAdmin();
AdminRole adminRole = entityManager.find(AdminRole.class,ADMIN_ROLE_ID);
terminalAdmin.getAdminRoles().add(adminRole);
entityManager.create(terminalAdmin);
}
}
I wrote it as an example of way doing it, this is not a ready-made code

Categories