I'm making a Spring app with Hibernate and MySQL. One of my tables has a composite primary key consisting of two foreign keys and a timestamp. I have created the table like this:
CREATE TABLE `trustAssessments` (
`deviceId` int not null,
`tmsId` int not null,
`trustLevel` float not null,
`honesty` float not null,
`cooperativeness` float not null,
`communityInterest` float not null,
`tstamp` timestamp not null,
primary key (`tmsId`, `deviceId`, `tstamp`),
key `FK_PEERTMS_idx` (`tmsId`),
constraint `FK_PEERTMS`
foreign key (`tmsId`)
references `peerTMS` (`id`)
on delete no action on update no action,
key `FK_TRUSTED_DEVICE_idx` (`deviceId`),
constraint `FK_TRUSTED_DEVICE`
foreign key (`deviceId`)
references `device` (`id`)
on delete no action on update no action
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
The corresponding Entity:
#Entity
#Table(name="trustassessments")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class TrustAssessment {
#EmbeddedId
#JsonView(Views.Public.class)
//#JsonUnwrapped
private TrustAssessmentId id;
#Column(name="trustLevel")
#JsonView(Views.Public.class)
private float trustLevel;
#Column(name="honesty")
#JsonView(Views.Public.class)
private float honesty;
#Column(name="cooperativeness")
#JsonView(Views.Public.class)
private float cooperativeness;
#Column(name="communityInterest")
#JsonView(Views.Public.class)
private float communityInterest;
#ManyToOne(cascade={CascadeType.PERSIST, CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH})
#JoinColumn(name="deviceId")
#MapsId("deviceId")
#JsonView(Views.Internal.class)
private Device device;
#ManyToOne(cascade= {CascadeType.PERSIST, CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH})
#JoinColumn(name="tmsId")
#MapsId("tmsId")
#JsonView(Views.Internal.class)
private PeerTms peerTms;
....
And the TrustAssessmentId embeddable class which defines the composite key:
#Embeddable
public class TrustAssessmentId implements Serializable {
#Column(name="deviceId")
int deviceId;
#Column(name="tmsId")
int tmsId;
#Column(name="tstamp", updatable=false, columnDefinition="TIMESTAMP")
#Temporal(javax.persistence.TemporalType.TIMESTAMP)
#JsonSerialize(as = Date.class)
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date tstamp = new Date();
....
I have two problems:
Problem 1: The first problem is that, when I use POST to create a new TrustAssessment, it is persisted, but the id value in the json in the response body is empty. I have checked with the debugger that the properties are not null.
For example, if I POST this:
{
"id": {
"deviceId": 21,
"tmsId": 20
},
"trustLevel": 0.4,
"honesty": 0.6,
"cooperativeness": 0.4,
"communityInterest": 0.2
}
I get this in the response body:
{
"id": {},
"trustLevel": 0.4,
"honesty": 0.6,
"cooperativeness": 0.4,
"communityInterest": 0.2
}
I can see in MySQL Workbench that it is persisted:
Problem 2: When I try to do a GET request to retrieve the persisted entity with this request body :
{
"id": {
"deviceId": 21,
"tmsId": 20,
"tstamp": "2019-11-29 19:20:21"
}
}
I get an empty response body, which means it can't find it. This also applies with DEL request which throws:
SEVERE: Servlet.service() for servlet [dispatcher] in context with
path [/tms-rest-again] threw exception [Request processing failed;
nested exception is java.lang.IllegalArgumentException: attempt to
create delete event with null entity] with root cause
java.lang.IllegalArgumentException: attempt to create delete event
with null entity at
org.hibernate.event.spi.DeleteEvent.(DeleteEvent.java:31) at
org.hibernate.internal.SessionImpl.delete(SessionImpl.java:951) at
com.cybertrust.tms.dao.TrustAssessmentDAOImpl.deleteTrustAssessment(TrustAssessmentDAOImpl.java:58)
at
com.cybertrust.tms.service.TmsServiceImpl.deleteTrustAssessment(TmsServiceImpl.java:202)
....
Related
I have two entity class as below -
public class Parent {
#Id
private Integer parentId;
private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> children;
}
public class Child {
#Id
private Integer childId;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parentId", insertable = false, updatable = true, nullable = false)
private Parent parent;
}
#RestController
public class ParentController {
#Autowired
private ParentRepo repo;
#GetMapping("/parent")
public void get() {
Child c1 = Child.builder().childId(1).name("s1").build();
Child c2 = Child.builder().childId(2).name("s2").build();
List<Child> children = new ArrayList<>();
children.add(c1);
children.add(c2);
Parent parent = Parent.builder().parentId(1).name("PARENT")
.children(children)
.build();
Parent savedParent = repo.save(parent);
}
}
Tables -
CREATE TABLE public.parent
(
parent_id integer NOT NULL,
name character varying(255) COLLATE pg_catalog."default",
CONSTRAINT parent_pkey PRIMARY KEY (parent_id)
)WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
CREATE TABLE public.child
(
child_id integer NOT NULL,
name character varying(255) COLLATE pg_catalog."default",
parent_id integer NOT NULL,
CONSTRAINT child_pkey PRIMARY KEY (child_id),
CONSTRAINT fk7dag1cncltpyhoc2mbwka356h FOREIGN KEY (parent_id)
REFERENCES public.parent (parent_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
I'm getting error while persisting child record.
Error -
Hibernate:
insert
into
child
(name, child_id)
values
(?, ?)
2022-07-19 23:12:31.727 WARN 20940 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 23502
2022-07-19 23:12:31.727 ERROR 20940 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: null value in column "parent_id" violates not-null constraint
Detail: Failing row contains (1, s1, null).
2022-07-19 23:12:31.728 INFO 20940 --- [nio-8080-exec-2] o.h.e.j.b.internal.AbstractBatchImpl : HHH000010: On release of batch it still contained JDBC statements
2022-07-19 23:12:31.754 ERROR 20940 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [parent_id]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
org.postgresql.util.PSQLException: ERROR: null value in column "parent_id" violates not-null constraint
Detail: Failing row contains (1, s1, null).
Not sure how hibernate will pick and assign the foreign key to child.
You have to set the bidirectional relationship first
public class Parent {
#Id
private Integer parentId;
private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> children;
public void addChild(Child child) {
this.children.add(child);
child.setParent(this);
}
}
and add the children via that method.
I need to map with JPA the following legacy DB table structure I cannot change.
It’s a one to many relationship between table ao_rda_acq (1) -> ao_rda_acq_righe (many) (purchase requisition -> purchase requisition rows)
Table (1)
create table ao_rda_acq
(
id_divisione varchar(4) not null,
esercizio smallint not null,
id_rda varchar(10) not null,
...
other fields
...
constraint pk_ao_rda_acq
primary key (id_divisione, esercizio, id_rda)
)
table many
create table ao_rda_acq_righe
(
id_divisione varchar(4) ,
esercizio smallint not null,
id_rda varchar(10) not null,
nr_riga integer not null,
...
other fields
...
constraint pk_ao_rda_righe
primary key (id_divisione, esercizio, id_rda, nr_riga),
constraint ao_rda_acq_righe_ao_rda_acq_id_divisione_esercizio_id_rda_fk
foreign key (id_divisione, esercizio, id_rda) references ao_rda_acq
)
The primary key of table ao_rda_acq side one of the relationship has 3 fields id_divisione, esercizio, id_rda. The primay key of the table side many of the relationship has the same 3 filed plus a 4th field nr_riga.
I tryed with this JPA approch using #IdClass annotation for composite primary keys
#Table(name="ao_rda_acq")
#Entity
#IdClass(RdaId.class)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Rda {
#Id
public String idDivisione;
#Id
public Integer esercizio;
#Id
public String idRda;
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JoinColumns({
#JoinColumn(name="id_divisione", referencedColumnName = "id_divisione"),
#JoinColumn(name="esercizio", referencedColumnName = "esercizio"),
#JoinColumn(name="id_rda", referencedColumnName = "id_rda")
})
#OrderBy("nrRiga")
public List<RdaRiga> righe = new ArrayList<>();
//Additional fields
}
where
public class RdaId implements Serializable {
String idDivisione;
Integer esercizio;
String idRda;
}
The entity for the rows is
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
#IdClass(RdaRigaId.class)
#Table(name = "ao_rda_acq_righe")
public class RdaRiga {
#Id
public String idDivisione;
#Id
public Integer esercizio;
#Id
public String idRda;
#Id
public Long nrRiga;
//More fields
}
where
public class RdaRigaId implements Serializable {
String idDivisione;
Integer esercizio;
String idRda;
Long nrRiga;
}
This code compiles but JPA at start-up complains with this message
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.DuplicateMappingException: Table [ao_rda_acq_righe] contains physical column name [id_divisione] referred to by multiple logical column names: [id_divisione], [idDivisione]
Is this approch the best one to map my unhappy DB structure? If so what did I miss?
When you use #Id without the #Column annotation, the name of the column is assumed to be the name of the annotated property.
Given that your DB column seems to be *id_divisione* you need to use also the annotation #Column(name = "id_divisione").
This also applies to the other properties annotated with #Id.
I have a really simple domain model: An 'Alert' has one 'Type' and one 'Status'.
This is my schema:
create table `price_alert_status` (
`id` bigint(20) not null,
`status_name` varchar(64) not null,
primary key (`id`),
unique key (`status_name`)
) engine=InnoDB default charset=utf8;
insert into `price_alert_status` values (0, 'INACTIVE');
insert into `price_alert_status` values (1, 'ACTIVE');
create table `price_alert_type` (
`id` bigint(20) not null,
`type_name` varchar(64) not null,
primary key (`id`),
unique key (`type_name`)
) engine=InnoDB default charset=utf8;
insert into `price_alert_type` values (0, 'TYPE_0');
insert into `price_alert_type` values (1, 'TYPE_1');
create table `price_alert` (
`id` bigint(20) not null auto_increment,
`user_id` bigint(20) not null,
`price` double not null,
`price_alert_status_id` bigint(20) not null,
`price_alert_type_id` bigint(20) not null,
`creation_date` datetime not null,
`cancelation_date` datetime null,
`send_periodic_email` tinyint(1) not null,
`price_reached_notifications` tinyint(4) default '0',
`approximate_price_notifications` tinyint(4) null,
`notify` tinyint(1) not null default '1',
primary key (`id`),
constraint `FK_ALERT_TO_ALERT_STATUS` foreign key (`price_alert_status_id`) references `price_alert_status` (`id`),
constraint `FK_ALERT_TO_ALERT_TYPE` foreign key (`price_alert_type_id`) references `price_alert_type` (`id`)
) engine=InnoDB default charset=utf8;
Now, I'm going to show the respective entity classes:
Alert.java:
// imports omitted
#Entity
#Table(name = "price_alert")
#EntityListeners(AuditingEntityListener.class)
#JsonIgnoreProperties(value = {"creationDate"},
allowGetters = true)
public class Alert implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private double price;
#OneToOne
#JoinColumn(name = "price_alert_status_id", nullable = false)
private Status status;
#OneToOne
#JoinColumn(name = "price_alert_type_id", nullable = false)
private Type type;
#Column(nullable = false, updatable = false)
#Temporal(TemporalType.TIMESTAMP)
#CreatedDate
private Date creationDate;
#Column(nullable = true)
#Temporal(TemporalType.TIMESTAMP)
private Date cancelationDate;
private boolean sendPeriodicEmail;
#Column(nullable = true)
private byte priceReachedNotifications;
#Column(nullable = true)
private byte approximatePriceNotifications;
private boolean notify;
// getters and setters omitted
}
Status.java:
//imports omitted
#Entity
#Table(name = "price_alert_status")
public class Status implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
#Column(name = "status_name")
#NotBlank
private String name;
//getters and setters omitted
}
Type.java:
//imports omitted
#Entity
#Table(name = "price_alert_type")
public class Type implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
#Column(name = "type_name")
#NotBlank
private String name;
//getters and setters omitted
}
Repositories:
AlertRepository.java:
//imports omitted
#Repository
public interface AlertRepository extends JpaRepository<Alert, Long> {
}
StatusRepository.java:
//imports omitted
#Repository
public interface StatusRepository extends JpaRepository<Status, Long> {
}
TypeRepository.java:
//imports omitted
#Repository
public interface TypeRepository extends JpaRepository<Type, Long> {
}
Now, the main controller:
AlertController.java:
#RestController
#RequestMapping("/api")
public class AlertController {
#Autowired
AlertRepository alertRepository;
#Autowired
StatusRepository statusRepository;
#Autowired
TypeRepository typeRepository;
#GetMapping("/alerts")
public List<Alert> getAllAlerts() {
return alertRepository.findAll();
}
#PostMapping("/alert")
public Alert createAlert(#Valid #RequestBody Alert alert) {
return alertRepository.save(alert);
}
#GetMapping("/alert/{id}")
public Alert getAlertById(#PathVariable(value = "id") Long alertId) {
return alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
}
#PutMapping("/alert/{id}")
public Alert updateAlert(#PathVariable(value = "id") Long alertId,
#Valid #RequestBody Alert alertDetails) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
alert.setApproximatePriceNotifications(alertDetails.getApproximatePriceNotifications());
alert.setCancelationDate(alertDetails.getCancelationDate());
alert.setNotify(alertDetails.isNotify());
alert.setPrice(alertDetails.getPrice());
alert.setPriceReachedNotifications(alertDetails.getPriceReachedNotifications());
alert.setSendPeriodicEmail(alertDetails.isSendPeriodicEmail());
alert.setUserId(alertDetails.getUserId());
// TODO: how to update Status and Type?
Alert updatedAlert = alertRepository.save(alert);
return updatedAlert;
}
#DeleteMapping("/alert/{id}")
public ResponseEntity<?> deleteAlert(#PathVariable(value = "id") Long alertId) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
alertRepository.delete(alert);
return ResponseEntity.ok().build();
}
}
So, I have two questions:
How can I create an alert, via POST, and associate existing status and type?
For example, this would be my cURL. I'm trying to indicate that I want to associate to this new alert the 'Status' and 'Type' existing objects, passing their respective IDs:
curl -H "Content-Type: application/json" -v -X POST localhost:8080/api/alert -d '{"userId": "1", "price":"20.0", "status": {"id": 0}, "type": {"id": 0}, "sendPeriodicEmail":false,"notify":true}'
Like the first question, how can I update an Alert, associating new existing 'Status' and 'Type' objects?
Thanks!
I think there is no out-of-the-box way to achieve this with a single POST request. The approach I see used most of the time is making an initial request to create the Alert, and subsequent requests to associate Status and Type.
You could take a look at how Spring Data Rest approaches the problem here:
https://reflectoring.io/relations-with-spring-data-rest/
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.association-resource
I'm not the biggest fan of Spring Data Rest though, since it forces some things (like hateoas) down your throat
,but you can easily implement the same approach manually.
You could argue that it's overkill to have separate calls to set the status and type of an alert, being both actually part of the alert, and I may agree actually. So if you don't mind slightly deviating from the rigidity of what people mostly call REST APIs (but are more like CRUD interfaces exposing your data model), it could make sense to take an AlertDto (with status and type ids) in your alert creation endpoint, retrieve status and type with these ids and create the Alert object you will eventually store.
Having said all of the above, I would avoid having tables for Status and Type if all they have is a name. I would have these names in the Alert itself and no relationships at all. Yes it may occupy more space on the database, but disk space is hardly a problem nowadays, and I'm guessing status and type are usually short strings.
I admit I am specially biased against this id-name lookup table pattern because we have dozens of these in one of our projects at work and they do nothing but generate a lot of useless code and complicate the DB schema.
Using hibernate and mysql 5.5, I am trying to persist String value in TEXT type column of database table.
Tired to set String value in the mentioned column and tried to persist the data.But i am getting following exception. I have generated Entity class using Netbeans 8.0.
Exception:-
FATAL: JSF1073: javax.faces.FacesException caught during processing of INVOKE_APPLICATION 5 : UIComponent-ClientId=, Message=/addNewCategory.xhtml #30,151 actionListener="#{categoryBean.addCategoryAction}": java.lang.AbstractMethodError: com.mysql.jdbc.ServerPreparedStatement.setCharacterStream(ILjava/io/Reader;J)V
FATAL: /addNewCategory.xhtml #30,151 actionListener="#{categoryBean.addCategoryAction}": java.lang.AbstractMethodError: com.mysql.jdbc.ServerPreparedStatement.setCharacterStream(ILjava/io/Reader;J)V
javax.faces.FacesException: /addNewCategory.xhtml #30,151 actionListener="#{categoryBean.addCategoryAction}": java.lang.AbstractMethodError: com.mysql.jdbc.ServerPreparedStatement.setCharacterStream(ILjava/io/Reader;J)V
at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:89)
CREATE SQL:-
CREATE TABLE `oc_category_description` (
`category_id` int(11) NOT NULL,
`language_id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`description` text NOT NULL,
`meta_description` varchar(255) NOT NULL,
`meta_keyword` varchar(255) NOT NULL,
PRIMARY KEY (`category_id`,`language_id`),
KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = #saved_cs_client */;
EntityClass
#Entity
#Table(name = "oc_category_description")
#XmlRootElement
public class OcCategoryDescription implements Serializable {
private static final long serialVersionUID = 1L;
#Basic(optional = false)
#NotNull
#Lob
#Size(min = 1, max = 65535)
#Column(name = "description")
private String description;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
//Constructors, setters, getters, equals and hashcode
}
Before raising question I went through following links which were of very little help.
JPA: how do I persist a String into a database field, type MYSQL Text
JPA and PostqreSQL: long string persistence
Issue persisting long strings with Hibernate
I also tried to persist data by removing #Lob, it saving data as "org.hibernate.engine.jdbc.BlobProxy#11e84b60"
I tried using #Column(columnDefinition = "TEXT") instead of #Lob, again its giving same result. "org.hibernate.engine.jdbc.BlobProxy#11e84b60"
I tried #Type(type="text")instead of #Lob, again its giving same result. "org.hibernate.engine.jdbc.BlobProxy#11e84b60"
remove the #Lob and #Size annotations, and use #Type(type="text")
instead
WORKED. Thanks #Maurice and #Funtik
I am new to JPA and Hibernate and I am trying to create an assignment table with an additional column position which is part of the PK which also contains a FK reference.
#Entity
#IdClass(ComponentAssignmentEntityPK.class)
public class ComponentAssignmentEntity implements Serializable {
#Id
private Integer containerID;
private Integer elementID;
#Id
private Integer position;
....
#ManyToOne
#PrimaryKeyJoinColumn(name="CONTAINERID", referencedColumnName = "ID")
public ComponentEntity getContainer() {
return container;
}
}
My Key class looks basically like this
public class ComponentAssignmentEntityPK implements Serializable {
private Integer containerID;
private Integer position;
....
}
However, if I now generate the init script using Hibernate it contains a duplicate definition for the containerid
create table ComponentAssignment (
containerID integer not null,
position integer not null,
...
elementID integer,
container_id integer not null, <===
primary key (containerID, position)
);
What am I doing wrong? I am using Hibernate 4.3.5.Final.
In your entity, you have defined two columns with same name CONTAINERID, one of them should be with different name.
As a result, when table creation script is generated, container_id is defined to make different
create table ComponentAssignment (
containerID integer not null, //refer to PK column name with #Id
position integer not null,
...
elementID integer,
container_id integer not null, //refer to column for ComponentEntity with #ManyToOne
primary key (containerID, position)
);
Assuming that it can be solved by renaming the column name for ComponentEntity like this;
#ManyToOne
#PrimaryKeyJoinColumn(name="container_id", referencedColumnName = "ID")