JPA not returning the records which contain empty columns in the EmbeddedId - spring

JpaRepository's findAll() method does not return the rows, if any of the field in the composite key is null.
This is the entity class with the EmbeddedId JobVaccinationPK
/**
* ApplicationParam entity. #author MyEclipse Persistence Tools
*/
#Entity
#Table(name="job_vaccination",schema="cdcis")
#SuppressWarnings("serial")
public class JobVaccination implements java.io.Serializable {
// Fields
#Column(name="default_yn", length=1)
private String defaultYn;
#EmbeddedId
private JobVaccinationPK jobVaccinationPK;
public JobVaccination(){
}
//setters getters
}
This is the Embedded class
#Embeddable
#SuppressWarnings("serial")
public class JobVaccinationPK implements Serializable{
#ManyToOne
#MapsId("job_category_id")
#JoinColumn(name = "job_category_id", nullable=true)
private JobCategoryTypeMast jobCategoryMast;
#ManyToOne
#MapsId("vaccination_id")
#JoinColumn(name = "vaccination_id", nullable=true)
private VaccinationMast vaccinationMast;
#ManyToOne
#MapsId("screening_type_id")
#JoinColumn(name = "screening_type_id", nullable=true)
private ScreeningTypeMast screeningTypeMast;
//getters and setters
}
Service implementation class
#Override
public SearchResult<JobVaccinationDto> getJobVaccination(JobVaccinationDto dto)
throws VaccinationException {
List<JobVaccination> vaccDetails = jobVaccinationRepo.findAll();
if(vaccDetails == null) return null;
List<JobVaccinationDto> jobVaccinationDtos = new ArrayList<JobVaccinationDto>();
jobVaccinationDtos = convertToDto(vaccDetails);
return new SearchResult<>(jobVaccinationDtos.size(), jobVaccinationDtos);
}
Here am able to insert a null value for either jobCategoryId or screeningTypeId, just like below row. But when I'm trying to fetch the rows which have empty values, it returns null. I've tried to debug but I was not able to find the cause.
This is the generated hibernate query:
Hibernate:
select
jobvaccina0_.job_category_id as job_cate4_13_,
jobvaccina0_.screening_type_id as screenin2_13_,
jobvaccina0_.vaccination_id as vaccinat3_13_,
jobvaccina0_.default_yn as default_1_13_
from
cdcis.job_vaccination jobvaccina0_
Hibernate:
select
jobcategor0_.job_category_id as job_cate1_11_0_,
jobcategor0_.job_category_name as job_cate2_11_0_,
jobcategor0_.job_category_name_ar as job_cate3_11_0_,
jobcategor0_.screening_type_id as screenin4_11_0_
from
cdcis.job_category_mast jobcategor0_
where
jobcategor0_.job_category_id=?
Hibernate:
select
screeningt0_.screening_type_id as screenin1_21_0_,
screeningt0_.active_yn as active_y2_21_0_,
screeningt0_.mmpid_required_yn as mmpid_re3_21_0_,
screeningt0_.screening_type as screenin4_21_0_
from
cdcis.screening_type_mast screeningt0_
where
screeningt0_.screening_type_id=?
Hibernate:
select
vaccinatio0_.vaccination_id as vaccinat1_27_0_,
vaccinatio0_.vaccination_name as vaccinat2_27_0_,
vaccinatio0_.vaccination_name_ar as vaccinat3_27_0_
from
cdcis.vaccination_mast vaccinatio0_
where
vaccinatio0_.vaccination_id=?
Going with #Adam Michalik answer. As a work-around I've introduced a new primary key field in the table, as we can't handle a null in the composite key.

Composite IDs cannot contain null values in any of the fields. Since the SQL semantics of NULL is that NULL <> NULL, it cannot be determined that a primary key (1, 2, NULL) is equal to (1, 2, NULL).
NULL means "no value" in SQL and its interpretation is up to you on a case-by-case basis. That's why SQL and JPA do not want to make assumptions that NULL = NULL and that a primary key containing a NULL identifies a single entity only.
You may choose to use a synthetic, generated primary key instead of the composite business primary key to overcome that. Then, you'd always have a non-null, single-column PK and nullable foreign keys.

change the data type of particular row in entity from int to integer

Related

Unknown Column Error while attempting a many-to-one entity mapping

I followed this example, renaming Student to Journey, and renaming Library to Station. My interpretation of the error is, the table schema does not have a field called 'departure' while the java class representation does. I didn't expect this to be a problem because I followed the JPA naming convention mentioned in this answer. In the example's case, lib + b_id == LIB_B_ID . In my case, departure + station_id == departure_station_id (my database naming scheme uses lowercase).
Other notable differences to the example, I don't have a persistence.xml, for id I use GenerationType.IDENTITY not GenerationType.AUTO .
Error
java.sql.SQLException: Unknown column 'departure' in 'field list'
Schema script
CREATE TABLE `station` (
`station_id` bigint(20) NOT NULL AUTO_INCREMENT,
`locker_id` bigint(20) DEFAULT NULL,
`station_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`station_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2579 DEFAULT CHARSET=utf8
CREATE TABLE `journey` (
`journey_id` bigint(20) NOT NULL AUTO_INCREMENT,
`departure_station_id` bigint(20) DEFAULT NULL,
`destination_station_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`journey_id`),
KEY `station_id_departure` (`departure_station_id`),
KEY `station_id_destination` (`destination_station_id`),
CONSTRAINT `journey_ibfk_1` FOREIGN KEY (`destination_station_id`) REFERENCES `station` (`station_id`),
CONSTRAINT `journey_ibfk_2` FOREIGN KEY (`departure_station_id`) REFERENCES `station` (`station_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Journey
#Entity
public class Journey implements Serializable{//Serializable required by #JoinColumn
//Station
public Journey(Station departure, Station destination){
this.departure = departure;
this.destination = destination;
}
//List<Station>
public Journey(List<Station> journeyStationList){
departure = journeyStationList.get(0);//0 is first index
destination = journeyStationList.get(journeyStationList.size() - 1);//-1 because first index of List is 0
}
private Long journeyId;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getJourneyId(){
return journeyId;
}
public void setJourneyId(Long journeyId){
this.journeyId = journeyId;
}
#ManyToOne
private Station departure;
public Station getDeparture(){
return departure;
}
public void setDeparture(Station departure){
this.departure = departure;
}
#ManyToOne
private Station destination;
public Station getDestination(){
return destination;
}
public void setDestination(Station destination){
this.destination = destination;
}
}
Station
#Entity
public class Station implements Serializable{//Serializable required by #JoinColumn
public Station(){//default constructor needed for JPA query
}
public Station(String stationName){
setStationName(stationName);
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long stationId;
public Long getStationId(){
return stationId;
}
public void setStationId(Long stationId){
this.stationId = stationId;
}
private String stationName;
public String getStationName(){
return stationName;
}
public void setStationName(String stationName){
this.stationName = stationName;
}
}
In contrast to the two examples I cross referenced A and B, I noticed in this question the #ManyToOne annotation was above the getter not the field's declaration. Because my currently working annotations (#id and #GeneratedValue) was also above a getter it seemed like a plausible solution. After moving the annotation to the getter, the error went away.
The examples were not wrong, they were just using a different access strategy than what I had inadvertently implicitly set by placing the #id annotation above the getter. By placing my #id annotation above the getter I implicitly specified property-based access resulting in hibernate calling the getter and setter methods to access my attributes, hence why my #ManyToOne annotation had to be above the getter. The examples I was referencing placed the #id annotation above the class variable itself, implicitly specifying field-based access in which case you should placing the #ManyToOne annotation above the class variables work. Source

Why doesn't Mybatis map a simple ENUM correctly?

I'm not doing anything out of the ordinary from what I can tell. I have a spring boot application using mybatis:
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
I have an application.properties config for mybatis that is pretty simple:
## MyBatis ##
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-statement-timeout=30
My database table looks like this:
CREATE TABLE workspace_external_references (
id CHAR(36) PRIMARY KEY,
workspace_id CHAR(36) NOT NULL,
site VARCHAR(255) NOT NULL,
external_id VARCHAR(255) NOT NULL,
created_at DATETIME(6) NOT NULL DEFAULT NOW(6),
updated_at DATETIME(6) NOT NULL DEFAULT NOW(6),
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE
)
With just a single entry like this:
'a907c0af-216a-41e0-b16d-42107a7af05f', 'e99e4ab4-839e-405a-982b-08e00fbfb2d4', 'ABC', '6', '2020-06-09 00:19:20.135822', '2020-06-09 00:19:20.135822'
In my mapper file I'm doing a select of all references like this:
#Select("SELECT * FROM workspace_external_references WHERE workspace_id = #{workspaceId}")
List<WorkspaceExternalReference> findByWorkspace(#Param("workspaceId") final UUID workspaceId);
And the java object that this is supposed to map to looks like this:
public class WorkspaceExternalReference {
private UUID id;
private UUID workspaceId;
private Sites site;
private String externalId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public WorkspaceExternalReference(
final Sites site,
final UUID workspaceId,
final String externalId) {
this.site = site;
this.workspaceId = workspaceId;
this.externalId = externalId;
}
}
public enum Sites {
ABC, XYZ;
}
Sooooo why doesn't this work? I get this error back:
Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'id' from result set. Cause: java.lang.IllegalArgumentException: No enum constant com.acme.Sites.a907c0af-216a-41e0-b16d-42107a7af05f
When there is no default constructor, you need to let MyBatis know which columns to pass to the constructor explicitly (in most cases).
With annotations, it would look as follows.
You can use <resultMap> and <constructor> in XML mapper.
#ConstructorArgs({
#Arg(column = "site", javaType = Sites.class),
#Arg(column = "workspace_id", javaType = UUID.class),
#Arg(column = "external_id", javaType = String.class)
})
#Select("SELECT * FROM workspace_external_references WHERE workspace_id = #{workspaceId}")
List<WorkspaceExternalReference> findByWorkspace(#Param("workspaceId") final UUID workspaceId);
Other columns (i.e. id, created_at, updated_at) will be auto-mapped via setters (if there are) or reflection.
Alternatively, you can just add the default (no-arg) constructor to the WorkspaceExternalReference class. Then all columns will be auto-mapped after the class is instantiated.
Note: To make it work, there needs to be a type handler registered for UUID, but you seem to have done it already (otherwise the parameter mapping wouldn't work).

How to query a database having a composite key, which accepts queries where parts of the composite key are null in Spring JPA?

I have a webapp that uses a database with a composite key. I need to create an API which will accept the parameters of the composite key wherein each paramenter can be null.
(for eg. If all the parameters of the composite key are set as null and query is done, then it should just return all rows in db)
I have used QueryByExampleExeccutor but it keeps throwing a null pointer exception.
Below is the model indicative of 1 row of the DB
public class Row
{
#EmbeddedId
private RowKey rowkey;
#Column
private String rowAttribute;
//getters and setters for rowkey and rowAttribute
}
Below is the model indicative of RowKey
public class RowKey
{
#Column(name = "rowBlock")
private String rowBlock;
#Column(name = "rowSection")
private String rowSection;
//getters and setters for above fields
}
I'm using QueryByExampleExecutor by extending my Repository(or DAO) with it as
public interface RowRepo extends CrudRepository<Row, RowKey>, QueryByExampleExecutor<Row> {
}
In my service layer I use the following statement to call repo methods to return a list of matched rows from DB
public List<Row> getRowRangeQuery(rowBlock,rowSection,rowAttribute)
{
Row row = new Row(new RowKey(rowBlock,rowSection),rowAttribute);
List<Row> Result = (List<Row>)getRowRepository().findAll(Example.of(row));
return Result;
}
I get an InvocationTargetException which when unrolled reveals a null pointer Exception at rowSection.

Spring JPA one to many

I have two entities :
#Entity
#Table(name="Registration")
public class Registration{
#Id
private UUID uuid;
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinColumn(name="registration", nullable = false)
private List<Payment> payment;
}
#Entity
#Table(name="Payment")
public class Payment {
#Id
private UUID uuid;
/*#ManyToOne(targetEntity = Registration.class) <-- MappingException: Repeated column in mapping for entity
private Registration registration;*/
}
This entities create two tables :
TABLE `registration` (
`uuid` binary(16) NOT NULL,
PRIMARY KEY (`uuid`))
TABLE `payment` (
`uuid` binary(16) NOT NULL,
`registration` binary(16) NOT NULL,
PRIMARY KEY (`uuid`),
CONSTRAINT `FK_jgemihcy9uethvoe3l7mx2bih` FOREIGN KEY (`registration`) REFERENCES `registration` (`uuid`))
I'm using Rest Service. I can access to
registration.payment
but not
payment.registration
why ? I need a relation oneToMany bidirectionnal ?
Yes, you need to add the payment.registration #ManyToOne relationship if you use it in your code.
Take into account that JPA allows you to map a SQL database model to an object oriented one. Once you have the mapping between your objects and your database, you always work at the object level. That's why, although you have the relationship in the database, your Payment object doesn't know anything about it unless you map it to an attribute.
Of course it applies when you are using you data model objects or performing JPQL or Criteria queries. If you use native queries you have access to the database model as it is.

Spring Hibernate - saving a one to many association

I would like to save a one to many relationship into the database. In this case one parent and two children.
DAO code
public void createMatch() {
UserEntity checker = new UserEntity();
UserEntity setter = new UserEntity();
setter.setChecker(checker);
checker.addSetter(setter);
if (checker != null)
sessionFactory.getCurrentSession().persist(checker);
}
The model code
public class UserEntity implements Serializable {
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumn(name="checker_id")
private UserEntity checker;
#OneToMany(mappedBy="checker", orphanRemoval=true, cascade = CascadeType.ALL)
private Set<UserEntity> setters = new HashSet<UserEntity>();
// getters and setters
public void addSetter(UserEntity setter) {
if(setters == null) {
setters = new HashSet<UserEntity>();
}
setter.setChecker(this);
this.setters.add(setter);
}
Jsp
Controller for post process
#RequestMapping(value="/student", method = RequestMethod.POST)
public ModelAndView hello(#ModelAttribute("checker") UserEntity checker) {
userService.createMatch();
return new ModelAndView ("redirect:/admin");}
The db it is saving to:
CREATE TABLE `user` (
`user_id` INT(11) NOT NULL AUTO_INCREMENT ,
`name` VARCHAR(45) NULL DEFAULT NULL ,
`checker_id` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`user_id`),
FOREIGN KEY (`checker_id`) REFERENCES `user` (`user_id`));
The problem is that one one child is saved for the parent, and when it is saved all of the fields are shown to be null except for the userId. I would like to persist more than one object for a parent and have the values show up in the database. I would like to ask how I go about implementing it?
Your hello() method takes a checker as argument, but ignores it completely. It calls createMatch(), without any argument, which saves an empty checker containing an empty setter. So the result is quite expected.
For the othe fields to be filled, you have to fill them.

Resources