Java Entity: Either manually setId or autogenerate from sequence - java

I have an entity with a composite key using #IdClass as following:
#Entity
#Builder
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(JsonInclude.Include.NON_EMPTY)
#JsonIgnoreProperties(ignoreUnknown = true)
#IdClass(MyId.class)
#SequenceGenerator(
name = "My_SEQUENCE",
initialValue = 1,
allocationSize = 1
)
public class MyEntity implements Serializable {
#Id
#Column(name = "id")
#GenericGenerator(
name = "MY_SEQUENCE",
strategy = "StringPrefixedSequenceIdGenerator",
parameters = {
#org.hibernate.annotations.Parameter(name = StringPrefixedSequenceIdGenerator.INCREMENT_PARAM, value = "1"),
#org.hibernate.annotations.Parameter(name = StringPrefixedSequenceIdGenerator.VALUE_PREFIX_PARAMETER, value = "ABC"),
#org.hibernate.annotations.Parameter(name = StringPrefixedSequenceIdGenerator.NUMBER_FORMAT_PARAMETER, value = "%07d") })
#GeneratedValue(strategy = GenerationType.IDENTITY, generator = "MY_SEQUENCE")
private String reqId;
#Id
#Column(name = "rev_no")
private Long revisionNumber;
...
}
And, it's repository as:
#Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {
}
And on my service I want following:
public class MyEntityService extends BaseService<MyEntityRepository, MyEntity, Long> {
#Transactional
public MyEntity create(Payload payload) {
...
myEntity.setRevisionNumber(1);
myEntityRepository.save(myEntity);
...
}
public MyEntity update(String reqId, Long revisionNumber, Payload payload) {
var myEntity = findByIdAndRevisionNumber(reqId, revisionNumber);
...
myEntity.setRevisionNumber(revisionNumber + 1);
myEntity.setReqId(reqId);
myEntityRepository.save(myEntity);
}
I have two cases:
On create, Auto-generate reqId using sequence defined in Entity.
On update, increment revision number manually and save new entity with the same reqId.
For case 1, it's working fine but on case#2 it's overriding reqId with auto-generated sequence.
Is there any way to setId manually so that generator won't override it?

Since you are using GenerationType.IDENTITY, you won't be able to use setters for particular attribute. It won't work.

Related

Setting OneToOne relationship in entity with inheritance

I created abstract class Entity (I want to create different types of shapes):
#Entity
#Inheritance(strategy = TABLE_PER_CLASS)
#Getter
#Setter
#NoArgsConstructor
public abstract class ShapeEntity {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
#OneToOne
private ShapeDetailsEntity shapeDetailsEntity;
public abstract double getArea();
public abstract double getPerimeter();
}
and I want to every add to every entity table with details:
#Entity
#Getter
#Setter
#Table(name = "shape_details")
#AllArgsConstructor
public class ShapeDetailsEntity {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
...
#OneToOne(cascade = CascadeType.ALL, mappedBy = "shapeDetailsEntity", fetch = FetchType.LAZY)
private ShapeEntity shapeEntity;
The logic of creating entites is in service:
public class ShapeService {
public ShapeEntity createShape(ShapeType type, List<Double> parameters) {
switch (type) {
case CIRCLE:
return circleEntityRepository.saveAndFlush(new CircleEntity(parameters));
case SQUARE:
return squareEntityRepository.saveAndFlush(new SquareEntity(parameters));
case RECTANGLE:
return rectangleEntityRepository.saveAndFlush(new RectangleEntity(parameters));
default:
throw new IllegalArgumentException();
}
}
and now for tests in controller I would like to create new entity - in comments I put response in console:
#PostMapping
public ResponseEntity<String> post(#Valid #RequestBody ShapeRequestModel shapeRequestModel) {
ShapeEntity shapeEntity = shapeService.createShape(ShapeType.valueOf(shapeRequestModel.getType()), shapeRequestModel.getParameters());
ShapeDetailsEntity shapeDetailsEntity = shapeService.createShapeDetails(shapeEntity);
System.out.println(shapeDetailsEntity.getShapeEntity().toString()); // -> CircleEntity{radius=4.5}
System.out.println(shapeDetailsEntity); // -> ShapeDetailsEntity{all details...}
System.out.println(shapeEntity.getShapeDetailsEntity().toString()); // -> java.lang.NullPointerException: null
return new ResponseEntity<>(shapeEntity.toString(), HttpStatus.CREATED);
}
in shapeService.createShapeDetails(shapeEntity) looks like:
public ShapeDetailsEntity createShapeDetails(ShapeEntity shapeEntity) {
ShapeDetailsEntity shapeDetailsEntity = new ShapeDetailsEntity();
shapeDetailsEntity.setShapeEntity(shapeEntity);
return shapeDetailsEntityRepository.saveAndFlush(shapeDetailsEntity);
}
How should I do it correctly to not getting null in
shapeEntity.getShapeDetailsEntity().toString())
? In database place when should be id of shapeDetailsEntity I am getting null.
The ShapeEntity you are creating has no ShapeDetailsEntity assigned i.e. the field is null, so that's why you get a NPE. If you don't want that, you have to assign an object to that field.
IMO, this shapeService.createShapeDetails(shapeEntity); code shouldn't be necessary. Your ShapeService.createShape method should assign a ShapeDetailsEntity object to the field.

JPA, Hibernate: fields of subclasses are null with #MappedSuperclass and InheritanceType.JOINED strategy

I'm trying to persist a Person entity, but I keep getting this null constraint violation error:
org.postgresql.util.PSQLException: ERROR: null value in column "created_by_party_id" violates not-null constraint
Detail: Failing row contains (5023, John, null, Smith, null, 1, null, null, 2020-11-11 07:33:31.590766-05, null, null, null).
What I have tried:
With and without the Discriminator annotations
Setting the value in #PrePersist
Initializing createdById in the base class with a default value
Making the Party class instantiable (not abstract), and persisting it directly - THAT WORKS, but it's not what I need
For some reason, the createdById is null by the time the SQL gets generated and passed off to to PostgreSQL. (I have verified in debug mode that this field is set, on the person entity, when it gets passed to the DAO save call.)
I'm using Spring boot, Hibernate, and PostgreSQL to map my tables and classes like this:
#MappedSuperclass
#EntityListeners( value = { EntityAuditListener.class } )
public abstract class BaseJPA {
#Column(name = "created_by_party_id", nullable = false)
private Long createdById = 1l;
/* Getters and Setters ... */
}
public class EntityAuditListener {
#PrePersist
public void prePersist(BaseJPA jpa) {
jpa.setCreatedById( 1l );
}
}
The Party class, although abstract, maps to a PARTY table:
#Entity(name = "Party")
#Table(name = "PARTY")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "PARTY_TYPE_CODE", discriminatorType = DiscriminatorType.INTEGER)
public abstract class Party extends BaseJPA implements Serializable {
private static final long serialVersionUID = 5434024967600745049L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PARTY_ID_SEQ")
#SequenceGenerator(name = "PARTY_ID_SEQ", sequenceName = "PARTY_ID_SEQ", allocationSize = 1)
#Column(name = "PARTY_ID")
protected Long partyId;
#Column(name = "PARTY_TYPE_CODE", insertable = false, updatable = false)
protected PartyType partyType;
/* Getters & Setters ... */
}
The PartyType enum is registered with an AttributeConverter, with #Converter(autoApply = true)
public enum PartyType {
PERSON(1), UNIT(2), SYSTEM(3);
private final int value;
PartyType(int value) {
this.value = value;
}
/* Getter */
}
#Entity(name = "Person")
#Table(name = "PERSON")
#DiscriminatorValue( "1" )
#Inheritance(strategy = InheritanceType.JOINED)
public class Person extends Party implements Serializable {
private static final long serialVersionUID = -5747077306637558893L;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
/* More fields ...*/
public Person() {
this.partyType = PartyType.PERSON;
}
/* Getters & Setters */
}
Are you sure that the entity listener method is called? Are you sure that there is no other method that might set the value to null?
If all that doesn't help, please create a reproducing test case and submit an issue to the Hibernate issue tracker.

HibernateException- identifier of an instance of Domain was altered from X to Y

I am getting an exception when I am updating Parent record in spring data jpa.
This is my code:
ParentEntity
#Entity
#Table(name = "CAMP")
#Getter
#Setter
public class Parent extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tkeygenerator")
#GenericGenerator(name = "tkeygenerator", strategy = "com.custom.TKeyGenerator",
parameters = {#org.hibernate.annotations.Parameter(name = "sequence", value = "TKEY_SEQ")})
#Column(name = "TKEY", nullable = false)
private String id;
#ManyToOne
#JoinColumn(name = "SUB_CAT_TYPE_CODE", referencedColumnName = "SUB_CAT_TYPE_CODE")
private Child child;
#Column(name = "DATE")
#Basic
private LocalDate date;
}
Child Entity
#Entity
#Table(name = "SUB_CAT_TYPE")
#AttributeOverrides({
#AttributeOverride(name = "code",
column = #Column(name = "SUB_CAT_TYPE_CODE", length = 30)),
#AttributeOverride(name = "description",
column = #Column(name = "SUB_CAT_TYPE_DESC", length = 255))})
#EqualsAndHashCode(callSuper = true)
public class Child extends AbstractTypeDesc {}
TestCode
public Parent update(#PathVariable("id") String id, #Valid #RequestBody UpdateDto dto) {
Parent parentObj = parentRepository.findById(id);
mapper.map(dto, parentObj); // Dozer to map incoming dto to domain
childRepository.findByCode(dto.child().getCode())
.map(child -> {
parentObj.setChild(child);
return child;
});
return parentRepository.save(parentObj); //Exception occurs here
}
I am getting an exception while trying to update code variable of child entity in parent entity as fk. It says can't alter code from X to Y.
Any suggestion?
I figured out what went wrong in above code block for update operation.Although not much clear why its happening.Dozer mapping which maps dto to Domain was causing issue it was changing the value of child entity and then again when i was trying to set child entity through setter method It was causing "Id alter exception", Though i thats the same thing i dont know why it was taking it differently. below is the working Working code.
Test code should be like
public Parent update(#PathVariable("id") String id, #Valid #RequestBody UpdateDto dto) {
Parent parentObj = parentRepository.findById(id);
childRepository.findByCode(dto.child().getCode())
.map(child -> {
parentObj.setChild(child);
return child;
});
mapper.map(dto, parentObj); // Dozer dto to domain mapping was causing problem
return parentRepository.save(parentObj);
}

Get Id of the associated object after save

There is simple model of associated entities:
MainModel class
#Entity
#Table(name = "main_model")
public class MainModel
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "id_main_model", referencedColumnName = "id")
private List<AssociatedModel> am_collection;
public MainModel() { }
public long getId()
{
return this.id;
}
public void addAssociated(AssociatedModel am)
{
am_collection.add(am);
}
}
AssociatedModel class
#Entity
#Table(name = "associated_model")
public class AssociatedModel
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
public AssociatedModel() { }
public long getId()
{
return this.id;
}
}
Simplest jpa repository for MainModel
public interface MainModelDAO extends JpaRepository<MainModel, Long>
{
}
and finally, controller's method for creating and saving AssociatedModel instance
#RequestMapping("/url")
public String createAssociated(#RequestParam("id_mainmodel") long id_mainmodel, #RequestBody AssociatedModel newAm)
{
MainModel mm = MainModelDAOobject.findOne(id_mainmodel);// MainModelDAOobject - #Autowired
mm.addAssociated(newAm);
MainModelDAOobject.saveAndFlush(mm);
return String.valueOf(newAm.getId());// get Id of associated saved object
}
Associated obect saves into database correctly, but id value of this object, allowed by its getId() method, always zero. How can I get Id of the saved associated object correctly?
Try calling MainModelDAOobject.flush() after saving the value, but before reading the ID.
See this answer for an explanation.
Edit: try manually saving newAm by adding a new line:
mm.addAssociated(newAm); // Existing line
newAm = MainModelDAOobject.save(newAm); // New line
MainModelDAOobject.save(mm); // Existing line
you need a sequencegenerator:
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MainModel SequenceGenerator")
#SequenceGenerator(name = "MainModel SequenceGenerator", sequenceName = "MainModel Sequence", initialValue = SEQUENCE_INITIAL_VALUE, allocationSize = SEQUENCE_ALLOCATION_SIZE)
Thanks to David Levesque for helpful suggestion.
problem is solved as follows:
need additional repository for AssotiatedModel
public interface AssotiatedModelDAO extends JpaRepository {}
then just save associated object manually:
mm.addAssociated(newAm);
newAm = AssotiatedModelDAOobject.save(newAm);
newAm = MainModelDAOobject.save(newAm)`;
MainModelDAOobject.save(mm);

Spring Data Repository Save Not Returning Instance With Updated Audit Fields

Why does repository.save(myEntity) not return an updated entity with the updated audit fields?
The resulting instance from MyEntityRepository.save(myEntity) and subsequently, from MyEntityService.save(myEntity) does not have the updated updatedOn date. I have verified this is correctly set in the database, so I know that auditing is working. The returned instance's updatedOn date is correct for an insert, but not for an update. I prefer to not have to immediately do a findById after every save, especially if the intent is that save() returns the udpated, attached instance.
Assuming the setting of updatedOn is occurring through a #PreUpdate hook and this hook is triggered during the entityManager.merge() call via repository.save(), I don't follow why the value would not be set on the returned instance.
Example code:
#Entity
#DynamicUpdate
#DynamicInsert
#Table(name = "my_entity", schema = "public")
#SequenceGenerator(name = "pk_sequence", sequenceName = "my_entity_seq", allocationSize = 1)
#AttributeOverrides({#AttributeOverride(name = "id", column = #Column(name = "id", columnDefinition = "int"))})
#EntityListeners(AuditingEntityListener.class)
public class MyEntity {
protected Integer id;
#LastModifiedDate
private Date updatedOn;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "pk_sequence")
#Column(name = "id", nullable = false, columnDefinition = "bigint")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Version
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated_on")
public Date getUpdatedOn() {
return updatedOn;
}
public void setUpdatedOn(Date updatedOn) {
this.updatedOn = updatedOn;
}
}
public interface MyEntityRepository extends JpaRepository<MyEntity, Integer> { }
#Service
#Transactional(readOnly = true)
public class MyEntityService {
#Autowired
private MyEntityRepository repository;
#Transactional
public MyEntity save(MyEntity myEntity) {
return repository.save(myEntity);
}
}
I faced with the same issue.
In my case the important items that helped me to solve this problem were:
1) use repository.saveAndFlush(...) method
2) use findAllById() or findByYourOwnQuery() (annotated with #Query).
Overall, my test case looked like this:
UserAccount userAccount = UserAccount.builder().username(username).build();
userAccountRepository.saveAndFlush(userAccount);
final LocalDateTime previousUpdateDate = userAccount.getUpdateDate();
....
List<BigInteger> ids = Arrays.asList(userAccountId);
UserAccount updatedUserAccount = userAccountRepository.findAllById(ids).get(0); // contains updated Audit data fields
...
assertThat(actual.getUpdateDate(), is(greaterThan(previousUpdateDate))); // true
The important thing that you shouldn't use repository.findOne(...) because it caches the reference to the object - read more.
I ran in to the exact same problem. I fixed it by using,
repository.saveAndFlush(myEntity);
instead of
repository.save(myEntity);

Categories

Resources