I´m refactoring a system to use Spring Boot/JPA/Hibernate.
There is a routine where the clients instances receive a list of objects from the central server to save. Each object from the list has an ID generated by the central server and I have to use the same ID when inserting on my clients instances, but this ID on my entity is an Autoincrement field.
My Entity:
#Entity
#Table(name = "usuario", schema = "adm")
#SequenceGenerator(name="seq_usuario_generator", sequenceName = "adm.seq_usuario", allocationSize=1)
public class UsuarioEntity implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_usuario_generator")
#Column(name = "id_usuario", unique = true, nullable = false)
private int id;
#Column(name = "name", length = 50)
private String name;
#Column(name = "active")
private Boolean active;
// getter and setter ommited
....
}
Should have something like this:
...
UsuarioEntity user = new UsuarioEntity();
user.setId(idFromCentralServer);
user.setName("Testing insert with given ID");
usuarioRepo.save(user);
...
When I execute this code, the given ID is ignored and a new one is generated from the local sequence.
Is there any way to save an object where I can set an ID without getting it from my sequence?
I still need to have the autoincrement feature, when the given ID is not provided.
Maybe this is not the most elegant solution I could end up with, but it solved my problem for now.
I created a custom sequence generator as follows:
public class CustomSequenceGenerator extends SequenceStyleGenerator implements Configurable {
private String sequenceCallSyntax;
#Override
public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
sequenceCallSyntax = "SELECT nextval('" + params.getProperty("sequence_name") + "')";
super.configure(type, params, serviceRegistry);
}
#Override
public Serializable generate(SessionImplementor s, Object obj) {
Serializable id = s.getEntityPersister(null, obj).getClassMetadata().getIdentifier(obj, s);
if (id != null && Integer.valueOf(id.toString()) > 0) {
return id;
} else {
Integer seqValue = ((Number) Session.class.cast(s)
.createSQLQuery(sequenceCallSyntax)
.uniqueResult()).intValue();
return seqValue;
}
}
}
And my entity got like this:
#Entity
#Table(name = "usuario", schema = "adm")
#GenericGenerator(name = "CustomSequenceGenerator",
strategy = "<....my_package...>.CustomSequenceGenerator",
parameters = {
#Parameter(name = "sequence_name", value = "adm.seq_usuario")
})
public class UsuarioEntity implements java.io.Serializable {
#Id
#GeneratedValue(generator = "CustomSequenceGenerator", strategy = GenerationType.SEQUENCE)
#Column(name = "id_usuario", unique = true, nullable = false)
private int id;
#Column(name = "name", length = 50)
private String name;
#Column(name = "active")
private Boolean active;
// getter and setter ommited
....
}
This way, when I save an entity with ID as null on my service, the generator returns the nextval from my sequence, and if I set an ID that was given by the central server it gets inserted in my table correctly.
If there is some more elegant solution, please let me know to update this answer.
Simply remove the #GeneratedValue annotation
#Id
#Column(name = "id_usuario", unique = true, nullable = false)
private int id;
Related
How to work multi-column constraint with JPA annotations in my springboot project?
I'm trying to introduce a multi-key constraint on a JPA-mapped entity:
#Entity
#Table(name = "Student_Master",uniqueConstraints={
#UniqueConstraint(columnNames = {"id","sr_no.", "roll_no"})
})
public class StudentMaster implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer sId;// id same
#Column(name = "sr_no.", length = 5,nullable = false,unique = true)//not null and sr no. should be unique
private Integer srNo;
#Column(name = "roll_no", length = 10,nullable = false,unique = true)
private Integer rollNo;
Basically (id, sr_no,roll_no) pair should be unique, but I only found a way to say that id should be unique.
I have also declare unique constraints using the #Table(uniqueConstraints = ...) annotation in your entity class.
But it's didn't work because entry the duplicate roll no and srNo also with different user.
You have the option to create an Embedded class, which contains your keys and reference it in your StudentMaster
For this you need #EmbeddedId in your Entity and #Embeddable over your key class:
#Embeddable
public class MyKey implements Serializable {
#Column(name = "id")
private Integer sId;// id same
#Column(name = "sr_no.", length = 5, nullable = false, unique = true)//not null and sr no. should be unique
private Integer srNo;
#Column(name = "roll_no", length = 10, nullable = false, unique = true)
private Integer rollNo;
/** getters and setters **/
}
#Entity
public class StudentMaster implements Serializable {
#EmbeddedId
private MyKey myKey;
}
Or a way without having the embedded class you can use #IdClass(MyKey.class).
Now you can use the #Id over your key attributes
public class MyKey implements Serializable {
private Integer sId;
private Integer srNo;
private Integer rollNo;
/** getters and setters **/
}
#Entity
#IdClass(MyKey.class)
public class StudentMaster implements Serializable {
#Id
#Column(name = "id")
private Integer sId;// id same
#Id
#Column(name = "sr_no.", length = 5, nullable = false, unique = true)//not null and sr no. should be unique
private Integer srNo;
#Id
#Column(name = "roll_no", length = 10, nullable = false, unique = true)
private Integer rollNo;
}
I'm new to Spring and I'm encountering a lot of doubts when making an insertion of the example below.
I currently have three tables whose models are those below:
#Entity
#Table(name = "namespace")
public class Namespace {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotNull
#Size(max = 100)
#Column(unique = true)
private String namespacename;
}
#Entity
#Table(name = "services")
public class Services {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotNull
#Size(max = 100)
#Column(unique = true)
private String servicename;
}
#Entity
#Table(name = "historiquedeploiement")
public class HistoriqueDeploiement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "idnamespace", nullable = false)
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#JsonIdentityReference(alwaysAsId=true)
#JsonProperty("idnamespace")
private Namespace namespace;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "idservices", nullable = false)
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#JsonIdentityReference(alwaysAsId=true)
#JsonProperty("idservices")
private Services services;
#NotNull
#Size(max = 100)
#Column(unique = true)
private String tagvalue;
}
And this is my DTO :
public class HistoriqueDeploiementReadingDTO {
#NotNull
private Integer id;
#NotNull
private String namespacename;
#NotNull
private String servicename;
#NotNull
private String tagvalue;
}
So the problem is :
I receive an object of type HistoriqueDeploiementReadingDTO and i have to insert it in historiquedeploiement table.
The problem is that i receive "namespacename", "servicename" and i need to save the id of each one that i can find in the table namespace and service.
When i have the id of each one, i can save it in historiquedeploiement table.
I hope you understand that little problem and hope you can purpose me something :)
Thanks !
You should first validate what you receive(against db records of each table). More or less the following will give you a highlight, so you should do for the others too.
Don't forget that all should be on the same transaction.
== updated==
#Transactional(rollbackFor=Exception.class)
public boolean saveHistoriqueDeploiement(HistoriqueDeploiementReadingDTO dto) {
Services service = getServices(dto.getServicename());
// do the same for the others
HistoriqueDeploiement deploiment = new HistoriqueDeploiement();
deploiment.setService(service);
//same for the others
deploiementRepository.save(deploiment);
}
public Services getServices(String serviceName) {
Services service = serviceRepository.findByServicename(serviceName); //if it exists, use the existing
if(service == null) {
service = new Services();
service.setServicename(dto.getServicename());
service = serviceRepository.save(service);
}
return service;
}
You have 2 ways:
First of all
if relation is many to one your field is List of services and List of namespaces instead services and namespaces.
if you mean OneToOne
HistoriqueDeploiementReadingDTO historiqueDeploiementReadingDTO;
NameSpace nameSpace = new Namespace();
namespace.setNameSpaceName(historiqueDeploiementReadingDTO.getNameSpaceName());
Services service = new Service();
service.setServicename(historiqueDeploiementReadingDTO.getServiceName())
HistoriqueDeploiement historiqueDeploiement = new HistoriqueDeploiement;
historiqueDeploiement.setTagValue(historiqueDeploiementReadingDTO.getTagValue())
historiqueDeploiement.setService(service)
historiqueDeploiement.setNameSpaceName(nameSpace)
IHistoriqueDeploiementRepository.save(historiqueDeploiement);
2 -
I have an embeddable class to hold a reference ID because I don't want to repeat the details of the column in every JPA entity.
#Data
#Embeddable
public class BusinessReference {
private static final int LENGTH = 64;
#Column(
columnDefinition = "VARCHAR(64)",
length = LENGTH,
updatable = false,
nullable = false,
unique = true)
#Size(min = LENGTH, max = LENGTH)
#Pattern(regexp = "^[!-~]+$")
private String referenceId;
}
Now I have an entity
#Data
#Entity
public class Sample {
#Id
#GeneratedValue
#Column(
updatable = false,
nullable = false,
columnDefinition = "BINARY(16)")
private UUID id;
#Embedded
private BusinessReference businessReference;
}
What I want to do is create a generic interface that would allow me to search by reference ID that I can use to extend another CrudRepository if needed. Note the parameter I want is the String rather than the object.
public interface BusinessReferenceRepository<T> {
Optional<T> getByBusinessReference(String referenceId);
}
I solved this by creating a default that would allow me to call it by the original type.
public interface BusinessReferenceRepository<T> {
Optional<T> getByBusinessReference(BusinessReference businessReference);
default Optional<T> getByBusinessReference(String referenceId) {
return getByBusinessReference(new BusinessReference(referenceId));
}
}
I am working on converting an existing project over to use Hibernate. I have a class like this:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#Column(name = "group_id_user")
private Long groupId;
#Column(name = "name")
private String name;
...
// getters and setters....
}
and a class like this:
#Entity
#Table(name = "group")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "group_id")
private Long groupId;
#Column(name="group_name")
private String groupName;
...
// getters and setters....
}
The column named "group_id_user" in the user table is supposed to be a foreign key to the column named "group_id" in the group table.
Is it okay or "correct" to have the classes structured as shown above or should they be structured as shown below to make sure that the foreign key exists in the Database?
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#ManyToOne
#JoinColumn(name = "group_id_user")
private Group group;
#Column(name = "name")
private String name;
...
// getters and setters....
}
and
#Entity
#Table(name = "group")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "group_id")
private Long groupId;
#Column(name="group_name")
private String groupName;
...
// getters and setters....
}
I have tried using both formats but have had issues both ways. When I use the first format I have issues with the HQL syntax for joins while creating queries. When I try the second format I have issues with fetching just a User from the database without a Group, or adding a new User from a json object the has a groupId instead of a Group object. So before I spend anymore time switching back and forth between the two formats I want to know for sure which way should I be using the annotations to best fit industry standard?
I would try something like this if you could change the name of the columns too:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY) //This means you will need the database to generate the ids, if you are using Oracle this won't work. You would need to use SEQUENCE.
private Long id;
#ManyToOne
#JoinColumn(name = "group_id") //There will be no need to specify the join column if you use group_id.
private Group group;
#Column(name = "name")
private String name;
...
// getters and setters....
}
#Entity
#Table(name = "groups")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="group_name")
private String groupName;
...
// getters and setters....
}
Also if you can I would change the name of the tables to plural.
Also I use something that helps me a lot. I have a super class called "Identifiable" which just has the id and it looks like this:
#MappedSuperclass
public class Identifiable implements Serializable {
private static final long serialVersionUID = -9027542469937539859L;
#Id
#Column(name = "ID")
#GeneratedValue
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Identifiable other = (Identifiable) obj;
if (id == null) {
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
With that you can extend all your classes with ids easily like:
#Entity
#Table(name = "users")
public class User extends Identifiable {
private static final long serialVersionUID = -90275424699375956859L;
#ManyToOne
#JoinColumn(name = "group_id") //There will be no need to specify the join column if you use group_id.
private Group group;
#Column(name = "name")
private String name;
...
// getters and setters....
}
However if you cannot change the names, let us know the issues you are having with the traces that are throwing and we might be able to help.
Thanks!
I have class for ID
#org.hibernate.annotations.AccessType("property")
public class ObjectID implements java.io.Serializable
{
private long value;
long getValue()
{
return value;
}
void setValue(Long id)
{
value = id != null ? id : 0L;
}
// equals, hash, contructor
}
And have mapped class (Patient) that used this ID-class. I want generate long value in ObjectID class. help me.
I tried
public class Patient implements Serializable
{
#javax.persistence.Id
#javax.persistence.Column(name = "aa_id")
#org.hibernate.annotations.Formula("case when aa_id is null then patient_seq.nextval else aa_id end")
#javax.persistence.AttributeOverride(name = "value", column = #Column(name = "aa_id"))
private ObjectID id;
}
and
public class Patient implements Serializable
{
#javax.persistence.Id
#javax.persistence.SequenceGenerator(name = "PatientSequenceGenerator",
sequenceName = "patient_seq")
#javax.persistence.GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "PatientSequenceGenerator")
#javax.persistence.AttributeOverride(name = "value", column = #Column(name = "aa_id"))
private ObjectID id;
}
But there are not helpful
One of resolves of this situation is to write custom userType for ObjectID and write custom ID-generator.
compositeIds are normally assigned by the program and i do not know if there is even a possibility to set it (or parts of it) through a databasesequence out of the box.
first you could try to set it this way to see if it works:
public class Patient implements Serializable
{
#Id
#SequenceGenerator(name = "PatientSequenceGenerator", sequenceName = "lab_patient_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PatientSequenceGenerator")
private Long id;
}