We are currently using JaVers 3.0.0. Suppose we have the following two entities A and B. And A keeps track of some Bs in a list.
#Entity
#TypeName("A")
public class A {
#Id
private int id;
private List<B> items;
public A() {
items = new ArrayList<>();
}
public A(int id) {
this();
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public List<B> getItems() {
return items;
}
public void setItems(List<B> items) {
this.items = items;
}
}
And here is our rather simple class B.
#Entity
#TypeName("B")
public class B {
#Id
private int id;
public B() {
}
public B(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
Lets commit three changes to an object of type A:
Commit object with empty list
Commit object with two added Bs
Commit object with one B removed
After that I want to observe changes on B.
Javers javers = JaversBuilder
.javers()
.build();
A a = new A(1);
javers.commit("foo#example.com", a);
a.getItems().add(new B(1));
a.getItems().add(new B(2));
javers.commit("foo#example.com", a);
a.getItems().remove(0);
javers.commit("foo#example.com", a);
List<Change> changes = javers.findChanges(
QueryBuilder.byClass(B.class)
.build());
String changelog = javers.processChangeList(changes, new SimpleTextChangeLog());
System.out.println(changelog);
The output says zero changes. I was expecting to see a removed object since B is an entity and has an Id. What am I missing?
Edit
Thanks for answering in the comments so far. Maybe I wasn't detailed enough. Sorry about that.
What I am trying to query is all the changes on A and all the changes on B. I only commit A, but maybe that is the problem? Should I track A and B?
javers.compare() and javers.commit() don't work in the same way.
compare() simply compares two object graphs, without any context.
That's why you could expect ObjectRemoved on the change list when comparing graphs.
But commit() is for auditing changes.
Since you've mapped both classes as Entities, they are independent. B objects can't be marked as deleted just because they are no longer referenced by A objects.
The only way to mark them as deleted (and to have ObjectRemoved change) is by calling commitShallowDelete()
Related
Say my DB looks like this, presenting using POJO:
class A {
long id; // auto-increment primary key
String aAttribute;
}
class B {
long id; // auto-increment primary key
long aId; // foreign key of class A
String bAttribute;
}
How could I naturally map the DB records to class B using JDBI so the class B could contain the actual object of A instead of a foreign key to A:
class B {
long id; // auto-increment primary key
A a; // actual object of class A
String bAttribute;
}
One approach (there are others, also) is to use the JDBI #Nested annotation with a bean mapper. The annotation:
"...creates a mapper for the nested bean."
Place the annotation on the relevant setter (or getter). So, in your case that would be like this:
import org.jdbi.v3.core.mapper.Nested;
import org.jdbi.v3.core.mapper.reflect.ColumnName;
public class B {
private long id; // auto-increment primary key
private A a; // actual object of class A
private String bAttribute;
#ColumnName("b_id")
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public A getA() {
return a;
}
#Nested
public void setA(A a) {
this.a = a;
}
#ColumnName("b_attribute")
public String getBAttribute() {
return bAttribute;
}
public void setBAttribute(String bAttribute) {
this.bAttribute = bAttribute;
}
}
I have also added #ColumnName annotations to disambiguate the otherwise identical column names between the two objects (and, presumably, the tables).
Here is class A:
package com.northcoder.jdbidemo;
import org.jdbi.v3.core.mapper.reflect.ColumnName;
public class A {
private long id; // auto-increment primary key
private String aAttribute;
#ColumnName("a_id")
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#ColumnName("a_attribute")
public String getAAttribute() {
return aAttribute;
}
public void setAAttribute(String aAttribute) {
this.aAttribute = aAttribute;
}
}
A query therefore needs to use column aliases to match these annotations:
String sql = """
select b.id as b_id, b.bAttribute as b_attribute, a.id as a_id, a.aAttribute as a_attribute
from your_db.a as a
inner join your_db.b as b
on a.id = b.a_id;
""";
jdbi.useHandle(handle -> {
List<B> bees = handle
.select(sql)
.mapToBean(B.class)
.list();
});
Each instance of class B in the resulting list will contain an instance of A (assuming the data exists in the database).
I am working with the Spring Boot Jpa Data Structure.
I have after successful test runs without a database been able to run my code flawlessly. After having hooked up my Spring Boot Application to a MySQL database and saving values into it, I run into the following issue:
Upon retrieving the data from database after restarting my application the List with the #ElementCollection is empty. I have checked the database and the data is present within the database, all other data is also retrieved without error. The application produces no error other than a Null Pointer which can be traced back to the value of the list being null.
Here is the code which is failing:
#Entity
public class Entity extends SuperEntity {
#ElementCollection
#LazyCollection(LazyCollectionOption.FALSE)
private List<String> flags;
public Entity() {
super();
this.flags = new LinkedList<>();
}
public Entity(List<String> flags) {
super();
this.flags = flags;
}
public Entity(#NotNull FormEntity formEntity) {
super();
this.flags = formEntity.getFlags();
}
public List<String> getFlags() {
return flags;
}
public void setFlags(List<String> flags) {
this.flags = flags;
}
}
#Entity
public abstract class SuperEntity {
protected #Id UUID id = UUID.randomUUID();
public SuperEntity() {}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
}
Any help is greatly appreciated and any additional materials needed from my end will be provided.
I've been using Spring Data for saving entities to the mongo DB and my code at the moment looks like this:
I have a repo class:
public interface LogRepo extends MongoRepository<Log, String> {
}
and I have an Entity Log which looks like this:
#Document(
collection = "logs"
)
public class Log {
#Id
private String id;
private String jsonMessage;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getJsonMessage() {
return jsonMessage;
}
public void setJsonMessage(String jsonMessage) {
this.jsonMessage = jsonMessage;
}
}
and this work well for me, however this approach works only for the case if I want to save Log entities to "logs" collection. However it would be very nice for me to be able to save Log entity to different collections depending on the context. I mean it would be nice to define collection name in the runtime. Is it possible somehow?
Thanks, cheers
Try to use inheritance and define appropriate collection names in such way. May give you possibility to save in different collections but you will be still not able to specify dynamically collection names and resp. their amount at runtime.
#Document(
collection = "logs"
)
public class Log {
#Id
private String id;
private String jsonMessage;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getJsonMessage() {
return jsonMessage;
}
public void setJsonMessage(String jsonMessage) {
this.jsonMessage = jsonMessage;
}
}
#Document(
collection = "log_child"
)
public class LogChild extends Log{}
With the MongoOperations save method you can choose which class to use and
based on the class it will choose the appropriate collection.
#Document(collection = "collection_#{T(com.github.your_project.DBUtils).getCollectionName()}")
public Class Collection
You can change the name in real time using a static getter
#UtilityClass
public class DBUtils {
private String collectionName;
public String getCollectionName() {
return collectionName;
}
public void setCollectionName(String collectionName) {
DBUtils.collectionName = collectionName;
}
}
i have an entity with a unique name.
In my example i save two persons with the same name. At the second time comes an "EntityExists" (Unique) Exception, that was the expected behavior.
After it i changed name and set the "ID" to null.
Than i try to persist it again but i get "org.apache.openjpa.persistence.EntityExistsException: Attempt to persist detached object "com.Person#1117a20". If this is a new instance, make sure any version and/or auto-generated primary key fields are null/default when persisting.
without the version it works but i find no solution to "reset" the version number.
Can someone help me?
Update: My new problem is, that i have a base entity an two pcVersionInit (look at my answer at bottom) i can't override it, i tried it in base and normal entity what is the best practise now instead of "override" the value in pcVersionInit ? Copy Constructor?"
public class Starter{
private static EntityManager em;
public static void main(String[] args) {
em = Persistence.createEntityManagerFactory("openjpa")
.createEntityManager();
Person p1 = new Person("TEST");
savePerson(p1);
Person p2 = null;
try{
p2 = new Person("TEST");
savePerson(p2);
}catch(Exception e){
p2.setId(null);
p2.setName(p2.getName()+"2");
em.persist(p2);
}
}
private static void savePerson(Person person){
em.getTransaction().begin();
em.persist(person);
em.getTransaction().commit();
}
}
Person.class:
#Entity
public class Person implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator="seqGenerator")
#SequenceGenerator(name="seqGenerator",sequenceName="personSeq")
private Long id;
#Version
private Long version;
#Column(nullable=true, unique=true)
private String name;
public Person(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
}
First off, stay away from messing around with pcVersionInit. I'd suggest to create a copy constructor in your Person Entity and in the event of rollback create a new one using the copy constructor.
Okay the problem is that OpenJPA adds a field named pcVersionInit (with #version) and set it "true" after try to persist. If i use reflection to set it to false, it works. Other way is a copy constructor.
I have two classes setup like the following. I am confused as to when I need to annotate something as an foreign collection and when I do not. This may also sound silly, but nowhere in the ORMLite documentation does it say whether or not a non-foreign collection is allowed. What if I have a List of ints which get autoboxed into Integers? can I just persist this using a standard #DatabaseField above the Collection? A foreign collection, according to ORMLite, must also have back reference for it to work (a reference to the parent, given a one to many realtionship). For the example below, I am assuming you should annotate myBList as a foreign collection as well as making myA a foreign object, but how could you handle myStringList?
I Have seen sample code here but it doesn't answer my questions: http://ormlite.com/docs/examples
public class A {
private Set<B> myBList = new HashSet<B>();
private List<String> myStringList = new ArrayList<String>();
private long id;
public A(){}
public Set<B> getMyBList() {
return myBList;
}
public void setMyBList(Set<B> myBList) {
this.myBList = myBList;
}
public List<String> getMyStringList() {
return myStringList;
}
public void setMyStringList(List<String> myStringList) {
this.myStringList = myStringList;
}
public void setId(long id){
this.id = id;
}
public long getId(){
return id;
}
}
public class B {
private int myInt;
private String myString;
private A myA;
private long id;
public B(){}
public A getMyA(){
return myA;
}
public A setMyA(A a){
myA = a;
}
public int getMyInt() {
return myInt;
}
public void setMyInt(int myInt) {
this.myInt = myInt;
}
public String getMyString() {
return myString;
}
public void setMyString(String myString) {
this.myString = myString;
}
public void setId(long id){
this.id = id;
}
public long getId(){
return id;
}
}
#Robert is correct. When hibernate persists a collection (or even an array), it does so with hidden extra tables with foreign ids -- in other words hidden foreign collections. ORMLite tries to adhere to the KISS principle and so has you define the foreign collections "by hand" instead.
I've added more details about storing collections.
http://ormlite.com/docs/foreign-collection
This means that you cannot persist an Integer type because there is no foreign-id. Also, your code can define a foreign collection Collection<Order> or ForeignCollection<Order>. Either one will be set with a ForeignCollection. ORMLite does not support lists or other collection types.
If you want to save a Collection (such as an ArrayList) of objects to ORMLite the easiest way is this:
#DatabaseField(dataType = DataType.SERIALIZABLE)
private SerializedList<MyObject> myObjects;
and to get my list of objects:
public List<MyObject> getMyObjects() {
return myObjects;
}