I am attempting to load a customized value into the #CreatedBy field in my AuditFields entity. Whenever I make a POST request, I am expecting Spring Data Rest to use this AuditorAwareImpl and pass the TestPrincipal string to the #CreatedBy field.
The result I am actually getting is 409 Conflict, SQL Constraint created_by is null.
The BaseEntity needs to use #MappedSuperclass but I think it needs the functionality of #Entity in order to pass the value through to #CreatedBy?
Any pointers here would be greatly appreciated.
AuditEntity
#Embeddable
#Access(AccessType.FIELD)
#Data
#EntityListeners(AuditingEntityListener.class)
public class AuditFields implements Serializable {
#CreatedBy
#Column(name="CREATED_BY", length=256)
private String createdBy;
#Column(name="CREATED_TIMESTAMP")
private LocalDateTime createTimestamp = LocalDateTime.now();
...
...
}
BaseEntity
#MappedSuperclass
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class BaseEntity implements Serializable {
#Column(name="RECORD_STATUS_CODE", length=1)
#EqualsAndHashCode.Exclude
private String myStatus;
#Version
#Column(name="VERSION_NUMBER")
#Setter(AccessLevel.PROTECTED)
#EqualsAndHashCode.Exclude
private Long versionNumber;
#Embedded
#EqualsAndHashCode.Exclude
private AuditEntity auditFields;
}
AuditorAwareImpl
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class AuditorAwareImpl {
#Bean
public AuditorAware<String> auditorProvider() {
return new AuditorAware<String>() {
#Override
public Optional<String> getCurrentAuditor() {
return Optional.of("TestPrincipal");
}
};
}
}
The trick here is that #CreatedBy doesn't work in an #Embedded entity without some extra work.
I pieced together a few different tutorials, the result being that I put the #EntityListener in my MyEntity and wrote a custom listener class that uses #PrePost. You can then build it out more by adding a BaseEntity that embeds the AuditEntity, etc.
AuditEntity
#Embeddable
#Data
public class AuditEntity {
#Column(name="CREATED_BY")
#CreatedBy
private String createdBy;
// additional fields ...
}
MyEntity
#Data
#Table(name="TABLENAME", schema="SCHEMANAME")
#EntityListeners(CustomAuditListener.class)
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="MY_ID")
private Long myId;
#Embedded
#EqualsAndHashCode.Exclude
private AuditEntity auditFields;
}
CustomAuditListener
public class CustomAuditListener {
#PrePersist
public void prePersist(Object obj) {
MyEntity entity = (MyEntity) obj;
AuditFields audit = new AuditFields();
audit.setCreatedBy("CreatedByValueHere")
entity.setAuditFields(audit);
}
// can add #PreUpdate, etc, here
}
Related
I am trying to map DTO to the corresponding #Entity in the service layer.
Condition may be of a set of types: Amount, Title, Date. Each condition, except Amount, has a unique predefined set of clauses.
TitleCondition: includes, startsWith
DateCondition: from, until
The idea is to use common Condition entity with #Inheritance(strategy= InheritanceType.TABLE_PER_CLASS).
The 2 problems I see with this code is:
It is unclear how to properly set data
data type is Object
Is there a way to use convenient Lombok's #Builder with given mapping? What would be the simpler and better way to map dto to entity?
Service:
#Service
public class FilterService {
private Condition convertConditionDtoToEntity(ConditionDto conditionDto) {
Type type = typeRepository.findFirstByName(conditionDto.getType())
.orElseThrow(UnsupportedOperationException::new);
Clause clause;
if (conditionDto.getClause() != null) {
clause = clauseRepository.findFirstByName().orElseThrow(UnsupportedOperationException::new);
}
if (conditionDto.getType().equals("amount")) {
return AmountCondition.builder().type(type).data(???).build();
} else if (conditionDto.getType().equals("title")) {
return TitleCondition.builder().type(type).clause(clause).data(???).build();
} else if (conditionDto.getType().equals("date")) {
return DateCondition.builder().type(type).clause(clause).data(???).build();
} else {
throw new UnsupportedOperationException();
}
}
}
Condition
#Getter
#SuperBuilder
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Inheritance(strategy= InheritanceType.TABLE_PER_CLASS)
public abstract class Condition {
#Id
#GeneratedValue
private long id;
#ManyToOne
private Filter filter;
#Getter
#ManyToOne
public Type type;
public abstract Object getData();
}
DateCondition
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class DateCondition extends Condition {
#Column
int clauseId;
#Column
#Temporal(TemporalType.DATE)
Date date;
#Getter
#ManyToOne
private Clause clause;
#Override
public Object getData() {
return date;
}
}
TitleCondition
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class TitleCondition extends Condition {
#Column
int clauseId;
#Column
String title;
#Getter
#ManyToOne
private Clause clause;
#Override
public Object getData() {
return title;
}
}
AmountCondition
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class AmountCondition extends Condition {
#Column
int amount;
#Override
public Object getData() {
return amount;
}
}
Clause
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Clause {
#Id
#GeneratedValue
private long id;
#Column
String name;
#OneToMany(mappedBy = "clause")
private Set<Type> types = new HashSet();
}
Type
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Type {
#Id
#GeneratedValue
private long id;
#Column
String name;
#ManyToOne
private Clause clause;
#OneToMany(mappedBy = "type")
private Set<Condition> conditions;
}
I have two forms that I use a lot and save a lot of time.
first: transform your model into json and convert the json to object the Mapper class (this link will help https://www.baeldung.com/jackson-object-mapper-tutorial)
second: spring has some cool functions about it. an example would be the BeanUtils.copyProperties function (source, target);
data problem: the data field does not exist for this reason you do not need to set it.
Another problem that I was able to notice is that their properties are all defaulted so set them as private.
First off - I know, it might seem like the same question has been asked a million times. However, this is related rather to DTOs, not entities nor missing cascades. If I create an entity myself and save it, everything is fine. The problem occurs when I create a DTO, convert it with ModelMapper and then try to save the converted entity.
If you look at the test class, the first test(saveCarByEntity) passes but the second(saveCarByDto) one produces the error.
Every class connected can be seen below.
Thank you in advance.
The entities:
#Data
#Entity
public class Car {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(mappedBy = "car", cascade = CascadeType.PERSIST)
private CarDetails carDetails;
}
#Data
#Entity
public class CarDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(mappedBy = "carDetails", cascade = CascadeType.PERSIST)
private Bumper bumper;
#OneToOne
private Car car;
}
#Data
#Entity
public class Bumper {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne
private CarDetails carDetails;
}
The DTO-s:
#Data
public class CarDto {
private Long id;
private CarDetailsDto carDetails;
}
#Data
public class CarDetailsDto {
private Long id;
private BumperDto bumper;
private CarDto car;
}
#Data
public class BumperDto {
private Long id;
private CarDetailsDto carDetails;
}
The test class:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
class CarTests {
#Autowired
private ModelMapper modelMapper;
#Autowired
private CarRepository carRepository;
#BeforeEach
public void setup() {
}
#Test
public void saveCarByEntity() {
Car car = new Car();
CarDetails carDetails = new CarDetails();
Bumper bumper = new Bumper();
car.setCarDetails(carDetails);
carDetails.setCar(car);
carDetails.setBumper(bumper);
bumper.setCarDetails(carDetails);
Car savedEntity = carRepository.save(car);
}
#Test
public void saveCarByDto() {
CarDto carDto = new CarDto();
CarDetailsDto carDetails = new CarDetailsDto();
BumperDto bumper = new BumperDto();
carDto.setCarDetails(carDetails);
carDetails.setCar(carDto);
carDetails.setBumper(bumper);
bumper.setCarDetails(carDetails);
Car car = modelMapper.map(carDto, Car.class);
Car savedEntity = carRepository.save(car);
}
}
Error produced:
nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.somepackage.model.Bumper.carDetails -> com.somepackage.model.CarDetails
CarRepository:
#Repository
public interface CarRepostiory extends JpaRepository<Car, Long> {
}
I don't know what your model mapper does, but I bet if you persist car details without the bumper and then the bumper, it will work. Maybe you can make it work by using CascadeType.PERSIST in the Bumper for carDetails as well?
Overview
I've got an #Entity with an #EmbeddedId composite key. The entities are exposed over a REST api which uses a BackendIdConverter to convert the id string back to an instance of the #Embeddable composite key. My understanding is that this is then used to identify an instance of the #Entity, but that isn't happening.
Question
How is the #Embeddable composite key resolved back to an #Entity?
Code
#Entity
public class MyEntity {
#EmbeddedId
private MyEntityIdentifier id;
#Embeddable
public class MyEntityIdentifier implements Serializable {
public static final String COMPOSITE_KEY_DELIMITER = "_#_";
#Column
private String idPartOne;
#Column
#Temporal(TemporalType.DATE)
private Date idPartTwo;
#Component
public class StringToMyEntityIdentifierConverter implements BackendIdConverter {
#Override
public Serializable fromRequestId(String id, Class<?> aClass) {
String[] split = id.split(COMPOSITE_KEY_DELIMITER);
String idPartOne = split[0];
Date idPartTwo = Date.valueOf(split[1]);
return new MyEntityIdentifier(fullName, lastUpdated);
}
public interface MyEntityRepository extends JpaRepository<MyEntity, MyEntityIdentifier> {
}
I have superclass:
#MappedSuperclass
public abstract class BaseEntity {
#Id #GeneratedValue
private Long id;
#Version
private long version;
}
and two subclasses:
#Entity
#Table(name = "\"user\"")
public class User extends BaseEntity {
private String username;
#org.hibernate.annotations.Type(type = "yes_no")
private boolean isAdmin;
// constructor/getters/setters etc.
}
#Entity
public class Product extends BaseEntity {
public String name;
public BigDecimal price;
// constructor/getters/setters etc.
}
I can query for all subclasses using code:
entityManager.unwrap(Session.class)
.createCriteria(BaseEntity.class)
.list()
.forEach(x -> System.out.println(x));
how I can get the same results via JPA (without unwrap, is it possible?). I tried using createQuery("from BaseEntity") but get BaseEntity not mapped exception.
EDIT: I know that this will result in two SELECT statement. And it must be MappedSuperclass - I would like to not change that.
I am new to hibernate and having a tough time trying to wrap my head around setting up Joined inheritance with composite Primary Key. With my current setup, I get a:
JDBCException: could not insert: LandHolidayPackage
I am essentially looking for two things:
Are the inheritance annotations in place ?
Is the composite PK setup properly ?
DB Design:
Reference
Here are my classes and the annotations involved:
#Entity
#Table(name = "HOLIDAYPACKAGE")
public final class HolidayPackage {
private Integer idPackage;
private String name;
private Set<HolidayPackageVariant> holidayPackageVariants = new HashSet<HolidayPackageVariant>(0);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "IDHOLIDAYPACKAGE", nullable = false)
public Integer getIdPackage() {
return idPackage;
}
#OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.ALL}, mappedBy = "holidayPackage")
public Set<HolidayPackageVariant> getHolidayPackageVariants() {
return holidayPackageVariants;
}
// ommitted other part of the code
}
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name="HOLIDAYPACKAGEVARIANT")
public abstract class HolidayPackageVariant {
private Integer idHolidayPackageVariant;
private HolidayPackage holidayPackage;
private String typeHolidayPackage;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="IDHOLIDAYPACKAGEVARIANT", nullable=false)
public Integer getIdHolidayPackageVariant() {
return idHolidayPackageVariant;
}
#ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
#JoinColumn(name="IDHOLIDAYPACKAGE", nullable=false)
public HolidayPackage getHolidayPackage() {
return holidayPackage;
}
#Column(name="TYPEHOLIDAYPACKAGE", nullable=true)
public String getTypeHolidayPackage() {
return typeHolidayPackage;
}
// ommitted setters, equals hashCode
}
#Entity
#Table(name="LANDHOLIDAYPACKAGEVARIANT")
public final class LandHolidayPackageVariant extends HolidayPackageVariant{
private static final String LAND = "LAND";
protected LandHolidayPackageVariant() {}
public LandHolidayPackageVariant(HolidayPackage holidayPackage) {
super(holidayPackage, LAND);
}
}
#Entity
#Table(name="FLIGHTHOLIDAYPACKAGEVARIANT")
public final class FlightHolidayPackageVariant extends HolidayPackageVariant{
private static final String FLIGHT = "FLIGHT";
private Destination originCity;
protected FlightHolidayPackageVariant(){}
public FlightHolidayPackageVariant(HolidayPackage holidayPackage,
Destination originCity) {
super(holidayPackage, FLIGHT);
setOriginCity(originCity);
}
#ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
#JoinColumn(name="IDDESTINATION", nullable=false)
public Destination getOriginCity() {
return originCity;
}
// ommited other setters etc functions
}
You annotated the properties in stead of the fields. JPA by default tries to access the fields. If you want JPA to use the fields you have to annotate the class with #AccessType(AccessType.Field).