I have a many to one relationship: A *<-->1 B and I want to deserialize A from a JSON having B's primary key (B exists in db with that primary key):
{
"b": 1
}
I have tried the following:
#Entity
#Table(name = "table_a")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class A implements Serializable {
#JsonIgnore
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "b", unique = true, nullable = false)
private B b;
}
and
#Entity
#Table(name = "table_b")
public class B implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#OneToMany(mappedBy = "b")
private List<A> a = new ArrayList<>();
}
but object A is created with b = null. How can I deserialize A with b property correctly instantiated from db?
Note: I am using Jackson version 2.6.1.
You have several options and here is similar question :
#JsonCreator factory in B class (More info)
Custom deserializer
Custom ObjectIdResolver for #JsonIdentityInfo like
private class MyObjectIdResolver implements ObjectIdResolver {
private Map<ObjectIdGenerator.IdKey,Object> _items = new HashMap<>();
#Override
public void bindItem(ObjectIdGenerator.IdKey id, Object pojo) {
if (!_items.containsKey(id)) _items.put(id, pojo);
}
#Override
public Object resolveId(ObjectIdGenerator.IdKey id) {
Object object = _items.get(id);
return object == null ? getById(id) : object;
}
protected Object getById(ObjectIdGenerator.IdKey id){
Object object = null;
try {
//can resolve object from db here
//objectRepository.getById((Integer)idKey.key, idKey.scope)
object = id.scope.getConstructor().newInstance();
id.scope.getMethod("setId", int.class).invoke(object, id.key);
} catch (Exception e) {
e.printStackTrace();
}
return object;
}
#Override
public ObjectIdResolver newForDeserialization(Object context) {
return new MyObjectIdResolver();
}
#Override
public boolean canUseFor(ObjectIdResolver resolverType) {
return resolverType.getClass() == getClass();
}
}
And use it like this:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
resolver = MyObjectIdResolver.class,
property = "id", scope = B.class)
public class B {
// ...
}
Here is your case related gist demo more broad github project with some serialization thoughts
Related
I have Spring Boot application (v3.0.2, Java 17), and in it, a simple entity ActivityType and corresponding ActivityDto.
//Entity (uses Lombok 1.18.24)...
#Getter
#Setter
#Entity
public class ActivityType {
#Id
#Column(name = "ActivityTypeId", nullable = false)
private Integer id;
#Column(name = "ActivityName", nullable = false, length = 30)
private String activityName;
#Column(name = "ActivityDescription")
private String activityDescription;
}
//DTO...
public record ActivityTypeDto(
Integer id,
String activityName,
String activityDescription) implements Serializable {
}
I'm using IntelliJ Idea (v2022.2.4) and JPA Buddy (v2022.5.4-222) to generate the Mapper Interface (MapStruct v1.5.3.Final). When I build the Mapper implementation, in the generated code, both the toEntity and toDto methods are incorrect.
#Component public class ActivityTypeMapperImpl implements ActivityTypeMapper {
#Override
public ActivityType toEntity(ActivityTypeDto activityTypeDto) {
if ( activityTypeDto == null ) {
return null;
}
ActivityType activityType = new ActivityType();
return activityType;
}
#Override
public ActivityTypeDto toDto(ActivityType activityType) {
if ( activityType == null ) {
return null;
}
// What's this all about?? Why not activityType.id, etc??
Integer id = null;
String activityName = null;
String activityDescription = null;
ActivityTypeDto activityTypeDto = new ActivityTypeDto( id, activityName, activityDescription );
return activityTypeDto;
}
#Override
public ActivityType partialUpdate(ActivityTypeDto activityTypeDto, ActivityType activityType) {
if ( activityTypeDto == null ) {
return activityType;
}
return activityType;
}
I've tried various alternatives, including using a class for the DTO instead of a record, but no success. Looks like I've missed something, but not sure what.
Update:
I can fix this by not using Lombok for the Entity getters/setters, which leads me on to final question, is there a setting on the MapStruct plugin to take Lomboz into account?
please define you entity like this,
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ActivityType {
#Id
#Column(name = "ActivityTypeId", nullable = false)
private Integer id;
#Column(name = "ActivityName", nullable = false, length = 30)
private String activityName;
#Column(name = "ActivityDescription")
private String activityDescription;
}
then define ActivityTypeDTO like this,
#Data
public class ActivityTypeDTO {
#JsonProperty("id")
private Integer id;
#JsonProperty("ActivityName")
private String ActivityName;
#JsonProperty("activityDescription")
private String activityDescription;
best practice to use MapStruct is like this,
#Mapper(componentModel = "spring", uses = {})
public interface ActivityMapper extends EntityMapper<ActivityTypeDTO, ActivityType> {
ActivityTypeDTO toDto(ActivityType activityType);
ActivityType toEntity(ActivityTypeDTO activityTypeDTO);
}
and EntityMApper in Mapper should be like this,
public interface EntityMapper<D, E> {
E toEntity(D dto);
D toDto(E entity);
}
Now I am sure you mapper work correctly.
Note: for simplyfication i have changed some variables names and get rid of unnecessary code to show my issue.
I have two repositories:
#Repository
public interface CFolderRepository extends CrudRepository<CFolder, Long>, QuerydslPredicateExecutor<CFolder> {}
#Repository
public interface CRepository extends JpaRepository<C, Long>, CFinder, QuerydslPredicateExecutor<C> {}
The class C is:
#FilterDef(name = "INS_COMPANY_FILTER", parameters = {#ParamDef(name = "insCompanies", type = "string")})
#Filter(name = "INS_COMPANY_FILTER", condition = " INS_COMPANY in (:insCompanies) ")
#NoArgsConstructor
#AllArgsConstructor
#Audited
#AuditOverrides({#AuditOverride(forClass = EntityLog.class),
#AuditOverride(forClass = MultitenantEntityBase.class)})
#Entity
#Table(name = "INS_C")
#Getter
public class C extends MultitenantEntityBase {
#OneToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "C_FOLDER_ID")
private CFolder cFolder;
public void addFolder(List<String> clsUrl){
this.cFolder = CFolder.createFolder(clsUrl);
}
}
CFolder is:
#Getter
#NoArgsConstructor
#Audited
#AuditOverride(forClass = EntityLog.class)
#Entity
#Table(name = "C_FOLDER")
#AllArgsConstructor
public class CFolder extends EntityBase {
#Column(name = "CREATION_FOLDER_DATE_TIME", nullable = false)
private LocalDateTime creationFolderDateTime;
#Column(name = "UPDATED_FOLDER_DATE_TIME")
private LocalDateTime updatedFolderDateTime;
#Column(name = "FOLDER_CREATED_BY", nullable = false)
private String folderCreatedBy;
#Column(name = "FOLDER_UPDATED_BY")
private String folderUpdatedBy;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "cFolder", fetch = FetchType.EAGER)
#NotAudited
private Set<FolderDocument> folderDocuments = new HashSet<>();
public static CFolder createFolder(List<String> clsUrl){
CFolder cFolder = new CFolder(LocalDateTime.now(), null, SecurityHelper.getUsernameOfAuthenticatedUser(), null, new HashSet<>());
createFolderDocuments(clsUrl, cFolder);
return cFolder;
}
public void updateFolder(List<String> clsUrl){
this.updatedFolderDateTime = LocalDateTime.now();
this.folderUpdatedBy = SecurityHelper.getUsernameOfAuthenticatedUser();
this.folderDocuments.clear();
createFolderDocuments(clsUrl, this);
}
private static void createFolderDocuments(List<String> clsUrl, CFolder cFolder) {
int documentNumber = 0;
for (String url : clsUrl) {
documentNumber++;
cFolder.folderDocuments.add(new FolderDocument(cFolder, documentNumber, url));
}
}
}
FolderDocument is:
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Audited
#AuditOverride(forClass = EntityLog.class)
#Entity
#Table(name = "FOLDER_DOCUMENT")
public class FolderDocument extends EntityBase {
#ManyToOne
#JoinColumn(name = "C_FOLDER_ID", nullable = false)
private CFolder cFolder;
#Column(name = "DOCUMENT_NUMBER", nullable = false)
private int documentNumber;
#Column(name = "URL", nullable = false)
private String url;
}
And finally we have a service in which i use these entities and try to save/load them to/from database:
#Service
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class CFolderService {
private final CRepository cRepository;
private final CommunicationClServiceClient communicationServiceClient;
private final CFolderRepository cFolderRepository;
public List<ClDocumentDto> getClCaseFolder(Long cId) {
C insCase = cRepository.findCById(cId);
List<ClDocumentDto> clDocumentsDto = getClDocuments(insCase.getCNumber()); // here, the object has one cFolder, but many FolderDocument inside of it
return clDocumentsDto;
}
#Transactional
public void updateCFolder(Long cId) {
C insC = cRepository.findCById(cId);
List<ClDocumentDto> clDocumentsDto = getClDocuments(insC.getCNumber());
List<String> clsUrl = clDocumentsDto.stream().filter(c -> "ACTIVE".equals(c.getCommunicationStatus())).map(ClDocumentDto::getUrl).collect(Collectors.toList());
if (Objects.isNull(insC.getCFolder())) {
insC.addFolder(clsUrl);
} else {
insC.getCFolder().updateFolder(clsUrl);
}
cFolderRepository.save(insC.getCFolder()); // here it saves additional FolderDocument instead of updateing it
cRepository.save(insC); // need second save, so can get these collection in getClaimCaseFolder successfully
}
}
I have two issues inside. In the example i was trying to clear the objects that i found from DataBase and create new ones.
1)
First is that i have to make two save operation to successfully restore the object in getClCaseFolder method (outside transactional).
2)
Second is that everytime i am saving - i get additional FolderDocument object pinned to CFolder object inside C object. I want to clear this collection and save new one.
I am not sure why hibernate does not update this object?
EDIT:
I think that i do sth like:
cRepository.save(insC);
instead of this.folderDocuments.clear();
i can do:
for(Iterator<FolderDocument> featureIterator = this.folderDocuments.iterator();
featureIterator.hasNext(); ) {
FolderDocument feature = featureIterator .next();
feature.setCFolder(null);
featureIterator.remove();
}
But i get eager fetching, why lazy wont work? There is an error using it.
Check whether you are setting ID in that Entity or not.
If ID is present/set in entity and that ID is also present in DB table then hibernate will update that record, But if ID is not present/set in Entity object the Hibernate always treat that object as a new record and add new record to the table instead of Updating.
I am looking way to store EnumSet in mysql column with type set:
#Data
#Entity
#Table(name = "ENTITY_TABLE")
public class Entity implements Serializable {
#Id
#GeneratedValue
#Column(nullable = false)
#NotNull
private String id;
#Column(name = "types")
private EnumSet<Type> types;
}
Enum of type is defined as below:
public enum Type {
TYPE1,
TYPE2,
TYPE3,
TYPE4,
TYPE5
}
And table is defined below:
CREATE TABLE `ENTITY_TABLE` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`types` set('TYPE1','TYPE2','TYPE3','TYPE4','TYPE5') DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
And insert in table:
INSERT INTO ENTITY_TABLE (types) VALUE ( 'TYPE1','TYPE2')
SET in mysql docs
Thanks a lot! I had to use a slightly different version what you have here. What worked for me was:
I had an Enum of permissions that needed to be adjustable:
#Convert(converter = SetConverter.class)
#Column(name = "permission")
private EnumSet<Permission> permission;
//in a util and imported
...
#Converter
public static class SetConverter implements AttributeConverter<EnumSet<Permission>, String> {
public String convertToDatabaseColumn(EnumSet<Permission> attribute) {
StringBuilder sb = new StringBuilder();
for (Permission c : attribute) {
sb.append(c + ",");
}
return sb.toString();
}
public EnumSet<Permission> convertToEntityAttribute(String dbData) {
if (dbData == null) {
dbData = "";
}
EnumSet<Permission> perm = EnumSet.of(Permission.DEFAULT); //default was a value I added.
String[] persistencePermissions = StringUtils.trimAllWhitespace(dbData).toUpperCase().split(",");
if (!StringUtils.isEmpty(StringUtils.trimAllWhitespace(dbData))) {
try {
for (String str : persistencePermissions) {
perm.add(Permission.valueOf(str));
}}
catch (IllegalArgumentException IAE) {
throw new Exception("INVALID_REQUEST");
}}
return perm;
}
}
Default JPA-Solution for Set
#Data
#Entity
#Table(name = "ENTITY_TABLE")
public class Entity implements Serializable {
#Id
#GeneratedValue
private String id;
#ElementCollection
#Enumerated(EnumType.STRING)
#Column(name = "types")
private Set<Type> types;
}
Another possibility would be with a AttributeConverter, But I have never tried this with MySQL set.
#Data
#Entity
#Table(name = "ENTITY_TABLE")
public class Entity implements Serializable {
#Id
#GeneratedValue
#Column(nullable = false)
#NotNull
private String id;
#Convert(converter = SetConverter.class)
#Column(name = "types")
private EnumSet<Type> types;
}
#Converter
public class SetConverter implements AttributeConverter<EnumSet<Type>, String> {
#Override
public String convertToDatabaseColumn(EnumSet<Type> attribute) {
// TODO Auto-generated method stub
return null;
}
#Override
public EnumSet<Type> convertToEntityAttribute(String dbData) {
// TODO Auto-generated method stub
return null;
}
}
I would like to create a repository which performs the basic CRUD operations.
Since I have different kind of photos (CompanyPhoto, CarPhoto, ..), I would prefer to make the JPA repository generic, but also the EJB service as well.
Here is my classes:
#Entity
#Inheritance
#DiscriminatorColumn(name = "DESCRIMINATOR")
#Table(name = "PHOTOS")
public abstract class Photo {
public Photo() {
}
public Photo(String fileName) {
this.fileName = fileName;
// this.file = file;
}
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "PHOTOS_SEQ")
#TableGenerator(name = "PHOTOS_SEQ", table = "SEQUENCE", pkColumnName = "SEQ_NAME", pkColumnValue = "PHOTOS_SEQ", valueColumnName = "SEQ_COUNT", allocationSize = 50)
#Column(nullable = false)
private long id;
#Column(length = 255)
#Size(min = 0, max = 255, message = "{Photo.description.size}")
protected String description;
#Column(nullable = false, length = 255)
#NotNull(message = "{Photo.fileName.notNull}")
#Size(min = 1, max = 255, message = "{Photo.fileName.size}")
protected String fileName;
// ...
#Entity
#DiscriminatorValue("C")
public class CarPhoto extends Photo {
public CarPhoto() {
}
public CarPhoto(String fileName) {
super.fileName = fileName;
}
#ManyToOne(cascade = { CascadeType.DETACH })
#JoinColumn(name = "CARID", nullable = false)
#NotNull
private Car car;
// ...
#Entity
#DiscriminatorValue("P")
public class PersonPhoto extends Photo {
public PersonPhoto() {
}
public PersonPhoto(String fileName) {
super.fileName = fileName;
}
#ManyToOne(cascade = { CascadeType.DETACH })
#JoinColumn(name = "PERSONID", nullable = false)
#NotNull
private Person person;
// ...
#Stateless
#LocalBean
public class PhotoRepository<E> {
// In this class I would like to do create, remove, update and some basic find //operations..
#PersistenceContext
private EntityManager em;
public PhotoRepository() {
}
PhotoRepository(EntityManager em) {
this.em = em;
}
#Override
public E create(E photo) {
em.persist(photo);
return photo;
}
#Override
public E modify(E photo)
{
Class<E> photoClass;
// QUESTION: How am I going to call the getId() method from the object of type E class?
em.find(photoClass, photo.getId()); // This will not work.. =(
E mergedPhoto = em.merge(photo);
return mergedPhoto;
}
// ...
I hope that you understand what I want to perform. A generic repository for different kind of classes which all inherit from the same baseclass. Can you give me some best practices examples?
=)
Best regards
Change the generics definition to say that E has to be any type that extends from Photo. Then you will be able to access methods of the Photo class on variables of type E
#Stateless
#LocalBean
public class PhotoRepository<E extends Photo> {
You can use the following method to retrieve the actual class.
public Class getEntityClass() {
ParameterizedType parameterizedType =
(ParameterizedType) getClass().getGenericSuperClass();
return (Class) parameterizedtype.getActualTypeArguments()[0];
}
Ir you are using spring you should also take a look at spring-data-jpa - it provides such generic repositories implementation.
You can get the Id from an Entity using,
entityManagerFactory.getPersistenceUnitUtil().getIdentifier(object);
Although you do not need to call find() before merge(), just call merge(), it will do the find if required.
I have a situation where I am working with EJB3 and a legacy database. I have a situation where there is a many-to-many relationship between two tables A and B, defined through a third (link) table L.
The complication is that the link table has other fields in it other than the PK's of tables A and B. The columns are standard timestamp and user columns to record who generated the link. These two additional columns are preventing me from defining the many-many relationship using a join table annotation, as they are not nillable and so must be populated.
Does anyone know of a way around this limitation? I could define One-to-many relationships from the link table to each of the other tables in the relationship, but this is not very elegant.
Thanks,
Yes, it is but you need to make it elegant. The following super-class can be used to define arbitrary many-to-many relationship as an entity:
#MappedSuperclass
public abstract class ModelBaseRelationship {
#Embeddable
public static class Id implements Serializable {
public Long entityId1;
public Long entityId2;
#Column(name = "ENTITY1_ID")
public Long getEntityId1() {
return entityId1;
}
#Column(name = "ENTITY2_ID")
public Long getEntityId2() {
return entityId2;
}
public Id() {
}
public Id(Long entityId1, Long entityId2) {
this.entityId1 = entityId1;
this.entityId2 = entityId2;
}
#Override
public boolean equals(Object other) {
if (other == null)
return false;
if (this == other)
return true;
if (!(other instanceof Id))
return false;
final Id that = (Id) other;
return new EqualsBuilder().append(this.entityId1, that.getEntityId1()).append(this.entityId1, that.getEntityId2()).isEquals();
}
#Override
public int hashCode() {
return new HashCodeBuilder(11, 111).append(this.entityId1).append(this.entityId2).toHashCode();
}
protected void setEntityId1(Long theEntityId1) {
entityId1 = theEntityId1;
}
protected void setEntityId2(Long theEntityId2) {
entityId2 = theEntityId2;
}
}
protected Id id = new Id();
public ModelBaseRelationship() {
super();
}
public ModelBaseRelationship(ModelBaseEntity entity1, ModelBaseEntity entity2) {
this();
this.id.entityId1 = entity1.getId();
this.id.entityId2 = entity2.getId();
setVersion(0);
}
#EmbeddedId
public Id getId() {
return id;
}
protected void setId(Id theId) {
id = theId;
}
}
The example of entity based on this super class (fragment):
#Entity(name = "myRealEntity")
#Table(name = "REAL_TABLE_NAME", uniqueConstraints = { #UniqueConstraint(columnNames = {
"FIRST_FK_ID", "SECOND_FK_ID" }) })
#AttributeOverrides( {
#AttributeOverride(name = "entityId1", column = #Column(name = "FIRST_FK_ID")),
#AttributeOverride(name = "entityId2", column = #Column(name = "SECOND_FK_ID"))
})
public class ModelBaseRelationshipReferenceImpl extends ModelBaseRelationship {
private Entity1OfManyToManyRelationship entity1;
private Entity2OfManyToManyRelationship entity2;
...
#ManyToOne
#JoinColumn(name = "FIRST_FK_ID", insertable = false, updatable = false)
public Entity1OfManyToManyRelationship getEntity1OfManyToManyRelationship() {
return entity1;
}
#ManyToOne
#JoinColumn(name = "SECOND_FK_ID", insertable = false, updatable = false)
public Entity2OfManyToManyRelationship getEntity2OfManyToManyRelationship () {
return entity2;
}
...
}