We are building an application that needs to store differences made on existing opbjects.
Afterwards we will store these changes to the database. However for some reason the #diffIgnore option is not working on our User.class.
Every object extends our baseEntityCMS class, which has a property User. This is meant to store our update user information after the compare by JaVers is done. For some reason the user object is still compared even after setting #diffIgnore on property level and on class level.
Here is the code:
BaseEntityCMS.java
public class BaseEntityCMS extends BaseEntity {
private Boolean active;
private LocalDateTime inactiveDateTime;
private LocalDate creationDate;// in original application
private LocalDate importDate;
private LocalDate startDate;
private LocalDate endDate;
#Embedded
#DiffIgnore
private User modifierUser;
...
}
CodeList.java
public class CodeList extends BaseEntityCMS {
private String companyCode;
private Application sourceApplication;
private String name;
private String format;
private int length;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "codeList")
private List<Description> descriptionList;
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "codeListList")
private List<Keyword> keywords;
private String domainOwner;
#OneToMany(cascade = CascadeType.REFRESH)
private List<Attribute> attributeList;
...
}
User.java
#Embeddable
#AllArgsConstructor
#Builder
#DiffIgnore
public class User {
private String userName;
}
TestCodeList.java
#Test
public void compareCodeListTest() {
Javers javers = JaversBuilder.javers().withListCompareAlgorithm(ListCompareAlgorithm.LEVENSHTEIN_DISTANCE)
.registerValueObject(BaseEntity.class).registerValue(Code.class).registerValue(Attribute.class)
.registerValue(AttributeValue.class).registerValue(MapCodeAttribute.class)
.registerValueObject(User.class).build();
CodeList codeListNew = createCodeListTest("ServiceTest");
CodeList codeListNew2 = createCodeListTest("ServiceTest");
Diff diff = javers.compare(codeListNew2, codeListNew);
assertNull(diff.getChanges());
}
So in the test class we create 2 CodeLists (codeListNew and codeListNew2) with a standard method.
Inside this method everything is created the same except we create a new User every time.
Because all properties of codeList (attribute, attributeValue, ...) extend the BaseEntityCMS class they all have a User property.
This is the output we get:
changes on xxx.CodeList/ :
- 'attributeList' collection changes :
1. 'Attribute(name=Address, format=freeformat, length=10, numberOfDecimals=2, description=description attribute, optional=true)' changed to 'Attribute(name=Address, format=freeformat, length=10, numberOfDecimals=2, description=description attribute, optional=true)'
0. 'Attribute(name=Number of employees, format=freeformat, length=10, numberOfDecimals=2, description=description attribute, optional=true)' changed to 'Attribute(name=Number of employees, format=freeformat, length=10, numberOfDecimals=2, description=description attribute, optional=true)'
- 'mapCodeAttributeMap' map changes :
'Code(super=BaseEntityCMS(super=BaseEntity(id=null), active=true, inactiveDateTime=null, creationDate=2020-03-23, importDate=2020-03-23, startDate=2020-03-23, endDate=2025-12-31, modifierUser=codems.agza.datalayer.model.User#57f83dc7), code=code1)' -> 'MapCodeAttribute(super=BaseEntity(id=null))' added
'Code(super=BaseEntityCMS(super=BaseEntity(id=null), active=true, inactiveDateTime=null, creationDate=2020-03-23, importDate=2020-03-23, startDate=2020-03-23, endDate=2025-12-31, modifierUser=codems.agza.datalayer.model.User#75937998), code=code1)' -> 'MapCodeAttribute(super=BaseEntity(id=null))' removed
We actually expect no difference since everything is the same but the User. This user is marked with #DiffIngore? How come we still have differences?
Forgot: we use
<dependency>
<groupId>org.javers</groupId>
<artifactId>javers-core</artifactId>
<version>5.8.11</version>
</dependency>
Related
I have two entities with fields that I´d like to localize. However, I´m not sure how to implement that correctly, because I would need to have a reference to the entities as well as a reference to the field that is translated, in order to have a shared "i18n" table.
#Entity
public class EntityA {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Translation> name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Translation> description;
}
Second entity
#Entity
public class EntityB {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Translation> name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Translation> shortDescription;
}
Translation Entity
#Entity
#Table(name = "i18n")
public class Translation {
private String languageCode;
private String translation;
//private String referenceToEntity
//private String referenceToField
}
Is there a given way to enable internationalization on entity fields in Spring or at least some kind of workaround to make it working without too much overhead?
EDIT
I´ve written a short post about how I solved it using XmlAnyAttribute https://overflowed.dev/blog/dynamical-xml-attributes-with-jaxb/
I did some research and found this #Convert JPA annotation. You would need to encapsulate the name and description properties into an object (that implements AttributeConverter), and use a convertion class to specify how it will be translated when persisted, and how will it be translated when retreived.
To execute translations on persistence and retrieval, you can consume Google translate's API.
Here:
#Entity
public class EntityA {
#Convert(converter = DescriptionConverter.class)
private Description description
// getters and setters
},
The encapsulated object, something like:
public class Description {
private String name;
private String language;
private String description;
// Getters and Setters.
}
And the translation applies here:
#Converter
public class DescriptionConverter implements AttributeConverter<Description, String> {
#Override
public String convertToDatabaseColumn(Description description) {
// consume Google API to persist.
}
#Override
public Document convertToEntityAttribute(String description) {
// consume Google API to retrieve.
}
}
this tutorial helped me a lot. i hope it will help you too. i used the second way and it's work perfectly.Localized Data – How to Map It With Hibernate
I use h2 in memory db and I don't want to create duplicate locations in my DataBase. Only when I use createItem and input location column id manualy it write it to the same location. Otherwise even if the country city gps coordinates are the same app write it to other location with it's id.
I tried to understand but It's not working
I got these entities.
#Entity
#Table(name = "item_System_items")
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String title;
private String description;
private BigDecimal price;
private Integer stock;
#ManyToOne
#JoinColumn(name = "location_id")
#Cascade(value={org.hibernate.annotations.CascadeType.ALL})
private Location location;
And
#Entity
#Table(name = "item_System_locations")
public class Location {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String country;
private String city;
private String street;
private String gpsCoordinates;
SETTERS AND GETTERS IS THERE I JUST NOT POST THEM HERE
Controller
#RestController
#RequestMapping("/items")
public class ItemsController {
#Autowired
private ItemsService service;
#PostMapping
#ResponseStatus(value=HttpStatus.CREATED)
public int createItem(#RequestBody Item item) {
return service.createItem(item);
}
Service
#Service
#Transactional
public class ItemsService {
#Autowired
private ItemJPARepository repository;
public int createItem(Item item) {
return repository.save(item).getId();
}
I expect after re-coding app doesn't make new location if the column values are the same.
Thank you people!
If you really help me I would be so happy!
There is nothing in your Entity definitions to tell the ORM about the constraint you want.
You can add #UniqueConstraint to the #Table in your Location entity and specify which column(s) must all be in a unique combination (example given in the linked documentation):
#Table(
name="EMPLOYEE",
uniqueConstraints=
#UniqueConstraint(columnNames={"EMP_ID", "EMP_NAME"})
)
This will add a check in the database, if the ORM is managing your database schema, which will throw an exception when violated.
I am trying to establish the aggregation relationship between two Java classes through JPA annotations in order to persist them into a database.
public class Ticket
{
private String ticketNo;
private Date releasedDate;
private boolean printed;
}
public class Discount
{
private String percentage;
private Date releasedDate;
private boolean printed;
}
Such as mentioned here, the aggregation relationship is unidirectional and thus, only it is necessary to map one side. From the solution given by this page, I think the solution will be:
public class Discount
{
private String percentage;
private Date releasedDate;
private boolean printed;
#ManyToOne(name="TICKET_ID")
private Ticket ticket;
}
However, in some examples of aggregation, the many side class appears inside the one side class. Thus, I am considering this too:
public class Ticket
{
private String ticketNo;
private Date releasedDate;
private boolean printed;
#OneToMany(mappedBy="ticket")
private List<Discount> discounts = new ArrayList<Discount>();
}
Which option is the proper one?
This how you map a unidirectional many-to-one relationship:
#Entity
public class Ticket {
#Id
#GeneratedValue
private Long id;
private String ticketNo;
private Date releasedDate;
private boolean printed;
// getters and setters
}
#Entity
public class Discount {
#Id
#GeneratedValue
private Long id;
private String percentage;
private Date releasedDate;
private boolean printed;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "TICKET_ID") // you can rename the join column
private Ticket ticket;
// getters and setters
}
Note:
JoinColumn (foreign key in the database terminology) must be on the many side of the relationship (this is the Discount in your case).
The #Id annotations are also mandatory. In this case, the ID will be generated by the persistence provider automatically. If you are using database sequence or table or some other strategy you can redefine it.
That looks right to me. A discount has a ticket. You could also include the discounts accessible from the tickets like ticket.getDiscounts() if you need to access them in a query such as SELECT t FROM Ticket t WHERE t.discounts.percentage >= :discountPercentage.
#Entity
public class Ticket {
#Id
private String ticketNo;
private Date releasedDate;
private boolean printed;
#OneToMany(mappedBy = "ticket", fetch = FetchType.LAZY)
private List<Discounts> discounts;
}
#Entity
public class Discount {
private String percentage;
private Date releasedDate;
private boolean printed;
#ManytoOne(name="TICKET_ID")
private Ticket ticket;
}
However, I wouldn't recommend using #OneToMany as this can create problems serializing too much data to JSON if you are returning this as JSON results or just lazily loading too much data by accident. You should always be able to work with just #ManyToOne as an example if you did not put the #OneToMany association query can be SELECT t FROM Discount d INNER JOIN d.ticket t WHERE d.percentage >= :discountPercentage
I'm using hibernate 4.0 with jpa and I've a one to many relationship that can load lots of data from database and I set it to lazy load (as the code bellow)
To keep the historic, i never remove the B from database when i want to delete it I simple set the "closed" attribute to true...
The problem is if i try to load all the A instances using:
session.createCriteria(A.class).list();
for each instance hibernate will lazy load the B what are markeds as closed. I would like to know if there are any annotation where i can define to only loads those with "closed" as false.
Avoiding to specify it at every code I use to load A
public class A {
#Id
private Integer id;
private String fullName;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, targetEntity = B.class)
#LazyCollection(LazyCollectionOption.TRUE)
private List<B> series = new Vector<B>();
}
public class B {
#Id
private Integer id;
private Boolean closed;
private Date createdDate;
/**lots of other things**/
}
I found the answer: http://www.mkyong.com/hibernate/hibernate-data-filter-example-xml-and-annotation/
by default jpa doenst allow it, but hibernate provide an annotation for this case, the solution was:
public class A {
#Id
private Integer id;
private String fullName;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, targetEntity = B.class)
#LazyCollection(LazyCollectionOption.TRUE)
#Filter(name="bNotClosed")
private List<B> series = new Vector<B>();
}
#FilterDef(name="bNotClosed", defaultCondition="closed= :value", parameters=
#ParamDef(name="value",type="boolean"))
public class B {
#Id
private Integer id;
private Boolean closed;
private Date createdDate;
/**lots of other things**/
}
session = HibernateUtils.getSessionFactory().openSession();
session.enableFilter("bNotClosed").setParameter("value", false);
if my parameters was an integer i could put it literally in defaultCondition="unliked = 123", therefore hibernate was interpreting false as a class attribute and going to error so i must define the value at session creation.
Spring LdapRepository save() method throws exception when I'm trying to update an existing object in LDAP database.
org.apache.directory.api.ldap.model.exception.LdapEntryAlreadyExistsException: ERR_250_ENTRY_ALREADY_EXISTS
What method should I use to update existing ldap objects?
Person class:
#Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" })
public class Person implements Serializable {
public Person() {
}
#Id
private Name dn;
#Attribute(name = "cn")
#DnAttribute(value = "cn")
#JsonProperty("cn")
private String fullName;
#Attribute(name = "uid")
private String uid;
private String mail;
#Attribute(name = "sn")
private String surname;
//setters and getters
}
Person repo interface:
public interface PersonRepo extends LdapRepository<Person> {
}
That's how I'm updating person:
personRepo.save(person);
Default implementation for Spring LDAP repositories is SimpleLdapRepository, that checks the property annotated with #Id to determine if the objects is new - and perform create, or old - and perform update.
I'm guessing that Person.dn is null when you're trying to perform update.
You also can take the control over this by implementing org.springframework.data.domain.Persistable and place your logic in the isNew() method.
See the implementation details.