I have the following class
public class ElementBean {
private String link;
private Set<ElementBean> connections;
}
I need to create a map table where elements are mapped to each other in a many-to-many symmetrical relationship.
#ManyToMany(targetEntity=ElementBean.class)
#JoinTable(
name="element_elements",
joinColumns=#JoinColumn(name="FROM_ELEMENT_ID", nullable=false),
inverseJoinColumns=#JoinColumn(name="TO_ELEMENT_ID", nullable=false)
)
public Set<ElementBean> getConnections() {
return connections;
}
I have the following requirements
When element A is added as a connection to Element B, then element B should become a connection of Element A. So A.getConnections() should return B and B.getConnections() should return A. I do not want to explicitly create 2 records one for mapping A to B and another for B to A.
Is this possible?
UPDATE : Thank you for all the suggestions.
When I try #Pascal's suggestion two records are created as follows when I try to connect element 1 with element 2.
FROM_ELEMENT_ID, TO_ELEMENT_ID
1, 2
2, 1
I want the records to be symmetrical where 1,2 is the same as 2,1
How is this issue dealt with?
Update
I explicitly created a map bean
class ConnectionBean {
ElementBean from;
ElementBean to;
}
#NotNull
#ManyToOne
#JoinColumn(name="FROM_ELEMENT_ID", nullable=false)
public ElementBean getFrom() {
return from;
}
#NotNull
#ManyToOne
#JoinColumn(name="TO_ELEMENT_ID", nullable=false)
public ElementBean getTo() {
return to;
}
I updated the ElementBean to
public class ElementBean {
private String link;
private Set<ConnectionBean> from;
private Set<ConnectionBean> to;
}
#OneToMany(mappedBy="from", fetch=FetchType.LAZY)
public Set<ConnectionBean> getFrom() {
return from;
}
#OneToMany(mappedBy="to", fetch=FetchType.LAZY)
public Set<ConnectionBean> getTo() {
return to;
}
Here I can control the insertion and deletion. For example before inserting a new ConnectionBean, I check to see if a connection exists between elements A & B by checking the Connection table for records where
((from == A && to == B) || (from == B && to == A))
before insertion.
When working with many to many, you don't have to worry too much about duplication as Hibernate will handle that for you. You do have to worry about making sure your object model is consistent and Hibernate won't necessarily help with that.
In your addConnection() method you should not have a getConnections() method that returns the actual connection, rather return a read only version, you'll need to make sure you handle both sides of the relationship:
addConnection(ElementBean element) {
if (element ==null) {
throw new IllegalArgumentException("cannot add null element");
}
connections.add(element);
element.getConnections0().add(this);
}
This means your requirement will hold and when it saves it will save okay. It will not save twice as it will notice that the one is a back reference for the other.
You should probably make the getConnections0() private or package.
This will be solved by hibernate's first level cache to keep track of the references as long as you map the m-2-m from both sides.
When working with bi-directional links, you need to take care of the link on both sides and, quoting the documentation:
Many developers program defensive and create a link management methods to correctly set both sides.
So in your case, I'd suggest to add the following methods:
public Set<ElementBean> getConnections() {
return connections;
}
public void setConnections(Set<ElementBean> connections) {
this.connections = connections;
}
public void addToConnection(ElementBean connection) {
this.getConnections().add(connection);
connection.getConnections().add(this);
}
public void removeFromConnection(ElementBean connection) {
this.getConnections().remove(connection);
connection.getConnections().remove(this);
}
Related
I have 2 entity. One of them User, and the other one is Followers. I'm trying to make a follower system like Twitter. User can follow another user but, i want to check if user followed, don't do it again.
This is my Follower Entity :
#Entity
public class Followers {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="from_user_fk")
#JsonIgnore
private User from;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="to_user_fk")
#JsonIgnoreProperties({ "password", "balance","id","mail" })
private User to;
public Followers() {};
public Followers(User from, User to) {
this.from = from;
this.to = to;
}
public User getFrom() {
return from;
}
public void setFrom(User from) {
this.from = from;
}
public User getTo() {
return to;
}
public void setTo(User to) {
this.to = to;
}
}
And this is the Service class :
public ResponseEntity<?> followUser(String username, User user) {
User byUsername = getByUsername(username);
List<Followers> followers1 = byUsername.getFollowers();
List<Followers> collect = followers1.stream().filter(p -> p.getTo().getId().equals(user.getId())).collect(Collectors.toList());
if(followers1.size()>0){
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("e");
}
Followers followers = new Followers();
followers.setFrom(user);
followers.setTo(byUsername);
followersRepository.save(followers);
return ResponseEntity.ok(new GenericResponse("Followed"));
}
public List<Followers> getUserFollowers(String username) {
User byUsername = getByUsername(username);
List<Followers> followers = byUsername.getFollowers();
return followers;
}
As you can see, I got the followers of the user I want to follow, and try to check if its following or not. But I couldn't.
Any help would be appreciated
What you've built is incredibly inefficient:
Go to the DB and fetch all followers for a given user.
Then check through these if the person you'd like to add already exists.
If no, add it.
If yes, don't do anything or show an error.
The fail whale is in your near future with this kind of inefficiency.
There's a much, much simpler way. Just.. add it! Don't do any check at all.
Your DB should be configured to disallow having the same user/follower pair, so if you attempt to pull that stunt when that user already has that follower, the DB will refuse and throw an exception that indicates that there's a DB constraint violation. That's your cue to render whatever error you please.
Note that 'check if X is allowed, if yes, do X' is fundamentally broken when talking about multicore architecture. What you're forgetting: What if the user 'double clicks' the 'add follower' link? Then 2 requests start, simultaneously. They both check if X is already a follower (they are not), then they both add X as follower (and now X is a double-follower which you did not want).
Generally if a DB is involved, it is better at data consistency and transactions support, so use the best tool for the job: A DB constraint.
I try to define immutable Event-Entites (Event Sourcing) using Java JPA/Hibernate and want those events to have an absolute ordering that already is defined right after object-creation, before any persistence has taken place (no distributed setup where one would need consensus)
We are using automatic auditing properties using #CreatedDate but I crossed that off my list since this is only populated during persistence.
I can think of 2 options:
using a global database-sequence that is queried during object-creation
Long ordering = System.nanoTime()
any advice/idea appreciated.
want those events to have an absolute ordering that already is defined
right after object-creation
The simpliest solution is like:
public class SomeEntity {
private LocalDateTime createdAt = LocalDateTime.now();
}
Of course it is possible that two objects will be created simultaneously and will have the same date, but it might be extremely hard to get.
A bit more complex - ordered by id if dates are equal and both objects are persisted. There may be indeterminacy if some of objects is not persisted yet, but if both are persisted - strict order is guaranteed:
public class SomeEntity implements Comparable<SomeEntity> {
private Long id;
private LocalDateTime createdAt = LocalDateTime.now();
#Override
public int compareTo(SomeEntity o) {
int result = createdAt.compareTo(o.createdAt);
if (result == 0) {
if (id != null && o.id != null) {
result = id.compareTo(o.id);
} else if (id != null) {
result = 1;
} else if (o.id != null) {
result = -1;
}
}
return result;
}
}
The most complex option but strict ordering is guaranteed: you can create counter service in your JVM and create events through factory, that will use that counter during event creation.
public class CounterService {
AtomicInteger counter = new AtomicInteger();
public int getNext() {
return counter.incrementAndGet();
}
}
public class SomeEntityFactory {
private CounterService counterService;
public SomeEntity create() {
return new SomeEntity(counterService.getNext());
}
}
public class SomeEntity {
private int order;
SomeEntity(int order) {
this.order = order;
}
}
Of course, this is example only, counter service might return BigInteger and be a web service, for instance. Or you can use a database sequence like a counter.
I'm trying to upgrade from Spring Data Neo4J 3 to 4 - I'm using Neo4J 2.2.2.
I use a GraphRepository instance to query the database, fetching back an object.
This object has several relationships, which are not fetched (deliberately, to avoid reading in the entire graph).
In the SDN3 code, simply used the Neo4JTemplate class to perform a fetch call for each relationship I needed to fetch. This worked extremely well.
However, in SDN4 this facility has been removed, and replaced by various implementations of the load() method. It's not clear from the documentation how to achieve what I did in SDN3.
To be clear: if I have a Set of objects in the first class I retrieve, governed by a relationship, I want to retrieve only the objects in that Set, not the entire collection of those objects in the database.
Have I missed something crucial in the upgrade process, or is there a simple way of doing what I'm trying to do?
Adding code:
My entity class:
#NodeEntity
public class File implements MetroNode {
private Long id;
private String fileName;
private SourceState sourceState;
private Set<State> states;
#GraphId
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
#Relationship(type = "HAS_FILE", direction = Relationship.INCOMING)
public SourceState getSourceState() {
return sourceState;
}
public void setSourceState(SourceState sourceState) {
this.sourceState = sourceState;
}
public State addState(MetroNode otherNode, StateStatus status) {
if (this.states == null) {
this.states = new HashSet<State>();
}
State state = new State(this, otherNode, status.toString());
this.states.add(state);
return state;
}
#Relationship(type = "HAS_STATE", direction = Relationship.OUTGOING)
public Set<State> getStates() {
return states;
}
public State getActiveState() {
if (this.states != null) {
for (State state : this.states) {
if (state.isActive()) {
return state;
}
}
}
return null;
}
}
My repository class:
public interface FileRepository extends GraphRepository<File> {
File findByFileName(String fileName);
}
When executing the getActiveState() method I get a null return, because the states Set is empty (hasn't been fetched).
Looking again at my code, I wonder if it's because I'm not using a "native" load method from the repository, but the overloaded version?
SDN 4 allows you to control loading of related entities with the persistence horizon.
Loading an entity with depth 0 will fetch properties of the entity and no related entities.
Depth 1 will fetch the first level of related entities, but not their relations and so on.
Controlling the depth by relationship type is not supported.
I am validating the status of a record retrieved from the DB by defining an ENUM as below
public enum RecordStatusEnum {
CREATED("CREATED"),
INSERTED("INSERTED"),
FAILED("FAILED");
private String recordStatusValue;
RecordStatusEnum (String status) {
recordStatusValue= status;
}
public boolean isSuccess() {
return (this.equals(CREATED) || this.equals(INSERTED));
}
}
The method isSuccess() is being used to check the status of the retrieved record ( column status from employee)
if (!(employee.getStatus().isSuccess())) {
// return error
}
As per the new requirement, there are a set of conditions introduced say A,B and C; and for them there is a column in the Employee table 'condition'.
So I need to retrieve the status as well as the condition and see if it belongs to a set which has the combination of both.
For eg : isSuccess() should check if in the following:
CREATED and A
CREATED and B
INSERTED and C
This must be achieved such that it is easy for me to add a new combination say 'INSERTED and B' into the list easily.
What is the best approach for the above problem?
Note : in the actual business scenario there are a whole lot more statuses and checks (eg isFailed() canBeModified() etc) with many different combinations
And any method can be suggested even if it doesn't use ENUMS. I mentioned ENUMS, because I dont want to deviate much from the existing implementation
There are many possibilities, but you could do like this (I removed the String status, which doesn't add any value since it's equal to the name of the enum):
public enum RecordStatusEnum {
CREATED(Condition.A, Condition.B),
INSERTED(Condition.C),
FAILED();
private Set<Condition> successConditions;
RecordStatusEnum(Condition... successConditions) {
this.successConditions = EnumSet.copyOf(Arrays.asList(successConditions));
}
public boolean isSuccess(Condition c) {
return successConditions.contains(c);
}
}
EDIT:
Example with two sets of conditions:
public enum RecordStatusEnum {
CREATED(EnumSet.of(Condition.A, Condition.B),
EnumSet.of(Condition.C)),
INSERTED(EnumSet.of(Condition.C),
EnumSet.of(Condition.B),
FAILED(EnumSet.noneOf(Condition.class),
EnumSet.noneOf(Condition.class));
private Set<Condition> successConditions;
private Set<Condition> modificationConditions;
RecordStatusEnum(Set<Condition> successConditions,
Set<Condition> modificationConditions) {
this.successConditions = successConditions;
this.modificationConditions = modificationConditions;
}
public boolean isSuccess(Condition c) {
return successConditions.contains(c);
}
public boolean canBeModified(Condition c) {
return modificationConditions.contains(c);
}
}
You could also compare the ordinal values, like so:
public enum RecordStatusEnum {
CREATED,
INSERTED,
UPDATED,
NEW,
FAILED,
FAILED_NO_DB,
FAILED_CONSTRAINT_VIOLATION;
public boolean isPersisted(RecordStatusEnum status) {
return status.ordinal < NEW.ordinal;
}
public boolean isError(RecordStatusEnum status){
return status.ordinal >= FAILED.ordinal;
}
}
In my domain model there are a lot of bidirectional associations (both OneToMany and ManyToMany)
I've read this article and made all my associations on the basis of the sample pattern. (the ManyToMany associations has a two-sided addXY methods, following the pattern)
Using the pattern in this article the question is, what about deleting from the inverse side?
Example:
public class Customer implements Serializable {
...
#ManyToOne()
private CustomerStatus customerStatus;
#PreRemove
public void preRemove(){
setCustomerStatus(null);
}
public void setCustomerStatus(CustomerStatus customerStatus) {
if(this.customerStatus != null) { this.customerStatus.internalRemoveCustomer(this); }
this.customerStatus = customerStatus;
if(customerStatus != null) { customerStatus.internalAddCustomer(this); }
}
On the other side:
public class CustomerStatus implements Serializable {
private static final long serialVersionUID = 1L;
#OneToMany(mappedBy="customerStatus")
private List<Customer> customers;
#PreRemove
public void preRemove(){
for(Customer c : customers){
c.setCustomerStatus(null); // this causes ConcurrentException
}
}
public List<Customer> getCustomers() {
return Collections.unmodifiableList(this.customers);
}
public void addCustomer(Customer c){
c.setCustomerStatus(this);
}
public void removeCustomer(Customer c){
c.setCustomerStatus(null);
}
void internalAddCustomer(Customer c){
this.customers.add(c);
}
void internalRemoveCustomer(Customer c){
this.customers.remove(c);
}
The problem is, that the preRemove method causes ConcurrentException. How to handle this?
The goal is, to delete the CustomerStatus, and set NULL all the Customers, where there was that status.
UPDATE
Without the preRemove method, I've got MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails
You cannot call this.customers.remove(c) while you are iterating over the customer collection. This question has come up before so you may find other solutions as in here:
How to avoid ConcurrentModificationException when iterating over a map and changing values?
but a simple solution is to just create a new list from the old to iterate over on preRemove:
public void preRemove(){
List<Customer> tempList = new ArrayList(customers);
for(Customer c : tempList){
c.setCustomerStatus(null);
}
}