In a Java (Spring Boot + Spring Data JPA) app, use a basic entity and build the inheritance as shown below:
#MappedSuperclass
public abstract class CookBase extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "cook_base_gen")
private long id;
// other fields
}
The following entities are inheriting from base entity:
#Entity
#SequenceGenerator(name = "cook_base_gen", sequenceName = "curb_side_id_seq", allocationSize = 1)
public class CurbSide extends CookBase {
public CurbSide(#NotNull UUID groupUuid, #NotNull String name) {
super(groupUuid, name);
}
}
#Entity
#SequenceGenerator(name = "cook_base_gen", sequenceName = "room_id_seq", allocationSize = 1)
public class Room extends CookBase {
public Room(#NotNull UUID groupUuid, #NotNull String name) {
super(groupUuid, name);
}
}
When running the app, I get the following warning on the console:
Duplicate generator name cook_base_gen
Duplicate generator name cook_base_gen
I think there may be a mistake regarding to the configuration of inheritance, but #SequenceGenerator(name = "cook_base_gen", sequenceName = "room_id_seq") seems ok I think. Any idea?
Related
This is my Entity class (shortened for obvious reasons):
Song.java:
#Entity
#Table(name = "songs", schema = "dbo")
public class Song {
#Id
#GeneratedValue
#Column(name = "song_id")
private Integer songId;
// other properties and getter setters not needed for the question
}
Here's my controller (again, shortened for obvious reasons):
SongsController.java:
#RestController
#RequestMapping("/songs")
public class SongsController {
#Autowired
private SongRepository songs;
//GET, DELETE and PUT mappings not neeeded for the question
//POST: adds a new song to the repository
#PostMapping("/add")
public void addSong(#RequestBody(required = true) Song song) throws DuplicateItemException {
if(songs.existsById(song.getSongId())) {
throw new DuplicateItemException(); //simplest possible custom exception handler imaginable
}
songs.save(song);
}
}
Here's my POST request from POSTMAN:
{
"songName": "Song3",
"songDuration": "490"
}
Here's what the function receives:
songName: "Song3"
songDuration: "490"
songId: null
The Exception that gets thrown is "songId cannot be null". How do I fix this?
My database is PostgreSQL (12.10.1).
You need to explicitly add strategy to generated value. Otherwise it will not work. Try this:
#GeneratedValue(strategy = GenerationType.AUTO)
Your #GeneratedValue is missing how to generate the value! Given that you're using PostgreSQL and such RDMS allows the creation of database sequences, I'll suggest the following configuration ... In your Song class, you need to add the #SequenceGenerator annotation and make the following changes:
#Entity
#Table(name = "songs", schema = "dbo")
#SequenceGenerator(
name = "<AnyNameThatFitsYou>",
sequenceName = "<TheNameOfYourDatabaseSequence>",
initialValue = <DatabaseSeqInitVal>,
allocationSize = <HowManyOfTheseValuesShouldBeAllocatedInJPAMemoryForFastAccess>
)
public class Song {
#Id
#GeneratedValue(
generator = "<TheNameYouGiveToTheSequenceGenerator>",
strategy = GenerationType.SEQUENCE
)
#Column(name = "song_id")
private Integer songId;
// other properties and getter setters not needed for the question
}
That should be all ...
is there a way to perform insertion of an #Id-column value into a non #Id column when entity is being created/updated to avoid double-save-ing?
#Entity
class DummyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "dummy_id_generator")
#SequenceGenerator(name = "dummy_id_generator", sequenceName = "dummy_id_seq")
Long id;
Long rootId;
}
#Repository
class DummyEntityRepo implements JpaRepository<DummyEntity, Long> {}
#Service
class DummyEntityService {
#Autowired
DummyEntityRepo repo;
void save(DummyEntityVO newDummyEntity) {
var newEntity = newDummyEntity.mapToEntity();
// !!! double save here !!!
repo.save(newEntity);
if(newEntity.getRootId() == null) {
newEntity.setRootId(newEntity.getId());
repo.save(newEntity);
}
}
}
What I was thinking was, since #Id is based on a sequence, I can try extracting next value from it and setting it as id and rootId before saving
Long nextId = // get next id somehow
newEntity.setId(newId);
if (newEntity.getRootId()) {
newEntity.setRootId(nextId);
}
repo.save(newEntity);
However:
I don't like perspective of interfering with Hibernate's id acquisition & saving methods
I found package org.springframework.jdbc.support.incrementer but unfortunately there is no sequence based OOB incrementer for my DB so it would require a custom implementation (found something promising here though)
#GeneratorType allows to generate/set values into entity during insertion phase. Suppose your entity looks like this:
#Entity
class DummyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "dummy_id_generator")
#SequenceGenerator(name = "dummy_id_generator", sequenceName = "dummy_id_seq")
Long id;
#GeneratorType(type = RecordIdGenerator.class, when = GenerationTime.INSERT)
Long rootId;
}
And your RecordIdGenerator class looks like:
public class RecordIdGenerator implements ValueGenerator<Long> {
#Override
public Long generateValue(Session session, Object owner) {
return ((DummyEntity)owner).getId();
}
}
Then when you'll insert new record hibernate will generate similar queries:
Hibernate: select nextval ('dummy_id_seq')
Hibernate: insert into dummy (record_id, id) values (?, ?)
and record_id will be equal to id
I need to use an existing sequence of the db to generate the id of this entity:
#Entity
#Table(schema="sistema", name="clientes_consultas")
public class Consulta implements Serializable {
#Id
#SequenceGenerator(schema = "sistema", sequenceName = "clientes_consultas_id_seq",
name = "seq_c", allocationSize = 1, initialValue = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_c")
protected Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
This is my sequence, and its stored on a schema that is not the postgres default 'public', its called 'sistema':
Unfortunately, theres an stacktrace saying that the sequence dont exists :(
Any clue?
Your sequence owner is "postgres". Maybe you defined different user than postgres in your connection.
i am getting this error while trying to save data into model in db.
#Entity
public class User extends Model {
#Required
public String name;
#Email
public String email;
#Required #MaxLength(value=10)
public String username;
#Required #MinLength(value=4)
public String password;
#Id
public int id;
}
this is my Class.
this is the error while i am trying to save the model into db.
i will appreciate any effort for help! many thanks.
EDIT: table structure is here
I think with ebean you have to physically name and annotate your id. You may also have to tell it the name of the backing sequencer as well (I dont remember). This shows how to do it.
This worked for me:
#Entity
#Table(name = "table", schema = "schema")
public class Bean extends Model{
#Id
#Column(name = "idcolumn")
#SequenceGenerator(name="gen", sequenceName="schema.table_idcolumn_seq",allocationSize=1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "gen")
private int id;
}
When using the SequenceGenerator, please mind this bug in Hibernate: https://hibernate.atlassian.net/browse/HHH-7232
It forces you to write the schema directly into the sequenceName instead of using the schema field in the SequenceGenerator annotation.
This worked for me on class annotation:
#SequenceGenerator(name = "SEQUENCE_NAME", sequenceName = "PST_BUSINESS.S_BUSINESS_DOMAIN")
#Entity
#Table(name = "TB_BUSINESS_DOMAIN", schema = "PST_BUSINESS")
public class PstBusinessDomain extends PstAbstractBaseMappedEntity {
As Leo said, this strategy works for annotation in the field and also in the class.
I'm using JPA2 with Hibernate and try to introduce a common base class for my entities. So far it looks like that:
#MappedSuperclass
public abstract class BaseEntity {
#Id
private Long id;
#Override
public int hashCode() {
// ...
}
#Override
public boolean equals(Object obj) {
// ...
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
}
However, for every table theres a sequence $entityname_seq which I want to use as my sequence generator. How can I set that from my subclass? I think I need to override #GeneratedValue and create a new SequenceGenerator with #SequenceGenerator.
Yes, it is possible. You can override the default generator name with the #SequenceGenerator annotation.
Base class
#MappedSuperclass
public abstract class PersistentEntity implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "default_gen")
protected Long id = 0L;
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
}
Sequence (SQL)
create sequence role_seq;
Derived class
#Entity
#Table(name = "role")
#SequenceGenerator(name = "default_gen", sequenceName = "role_seq", allocationSize = 1)
public class Role extends PersistentEntity implements Serializable
{
private static final long serialVersionUID = 1L;
#NotNull
#Size(max = 32)
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
This approach worked fine in Hibernate 4.1.x, but it didn't in EclipseLink 2.x.
edit
As per the comment, it seems to be working with EclipseLink 2.6.1-RC1.
In JPA that cannot be done with annotations. Annotation itself cannot be overridden. Entity inherits all the mapping information from MappedSuperClass. There is only two annotations that can be used to redefine mappings inherited from mapped superClass:
AttributeOverride to override column mappings and
AssociationOverride to override join columns / table.
Neither of them helps with GeneratedValue.
With EclipseLink, you can use a Customizer. DescriptorCustomizer interface defines a way to customize all the information about a jpa descriptor (aka a persistent entity).
public class SequenceCustomizer implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) throws Exception {
descriptor.setSequenceNumberName(descriptor.getTableName());
}
}
and in your mapped superclass:
#MappedSuperclass
#Customizer(SequenceCustomizer.class)
public abstract class AbstractEntity implements Serializable {
...
}
I'm writing this as it gets too unreadable as the comment on the accepted answer:
I have a BaseEntity that every other Entity inherits from:
BaseEntity.java:
#MappedSuperclass
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_ID")
private Long id;
I then have two Entities User and Order that both inherit from BaseEntity whilst also having the #SequenceGenerator annotation:
User.java:
#SequenceGenerator(name = "SEQ_ID", sequenceName = "SEQ_USER", allocationSize = 1)
public class User extends BaseEntity { ... }
Order.java:
#SequenceGenerator(name = "SEQ_ID", sequenceName = "SEQ_ORDER", allocationSize = 1)
public class Order extends BaseEntity { ... }
It works on H2 at least with 2 Sequences SEQ_USER and SEQ_ORDERS:
select SEQ_USER.nextval from dual;
select SEQ_ORDERS.nextval from dual;