I need to create a single database table to store different changelog/auditing (when something was added, deleted, updated). I want to capture
id (for the event),
user that triggered it,
entity name,
entity id,
timestamp of the event
Through Hibernate inceptor I can able to audit both insertion and updation operation in the audit table but failed to audit the deleted entries.It doesn't throw any error but it gets rolled back somewhere.Can you provide the solution?
The interface is
public interface IAuditLog {
public Long getId();
}
The audit entity class is
#Entity
#Table(name = "auditlog")
public class AuditLog implements java.io.Serializable {
private Long auditLogId;
private String action;
private String detail;
private Date createdDate;
private long entityId;
private String entityName;
...
}
#Entity
#Table(name = "stock")
public class Stock implements java.io.Serializable, IAuditLog {
...
#Override
public Long getId(){
return this.stockId.longValue();
}
}
Interceptor class
public class AuditLogInterceptor extends EmptyInterceptor{
private Set inserts = new HashSet();
private Set updates = new HashSet();
private Set deletes = new HashSet();
#Autowired private static AuditRepo auditRepo;
public boolean onSave(Object entity,Serializable id,
Object[] state,String[] propertyNames,Type[] types)
throws CallbackException {
System.out.println("onSave");
if (entity instanceof IAuditLog){
inserts.add(entity);
}
return false;
}
public boolean onFlushDirty(Object entity,Serializable id,
Object[] currentState,Object[] previousState,
String[] propertyNames,Type[] types)
throws CallbackException {
System.out.println("onFlushDirty");
if (entity instanceof IAuditLog){
updates.add(entity);
}
return false;
}
public void onDelete(Object entity, Serializable id,
Object[] state, String[] propertyNames,
Type[] types) {
System.out.println("onDelete");
if (entity instanceof IAuditLog){
deletes.add(entity);
}
}
//called after committed into database
public void postFlush(Iterator iterator) {
try{
for (Iterator it = inserts.iterator(); it.hasNext();) {
IAuditLog entity = (IAuditLog) it.next();
System.out.println("postFlush - insert");
logIt("Saved",entity);
}
for (Iterator it = updates.iterator(); it.hasNext();) {
IAuditLog entity = (IAuditLog) it.next();
System.out.println("postFlush - update");
logIt("Updated",entity);
}
for (Iterator it = deletes.iterator(); it.hasNext();) {
IAuditLog entity = (IAuditLog) it.next();`enter code here`
System.out.println("postFlush - delete");
logIt("Deleted",entity);
}
} finally {
inserts.clear();
updates.clear();
deletes.clear();
}
}
private void logIt(String action, Object entity) {
try {
...
//setting into auditlog
auditRepo.save(auditLog);
} catch (HibernateException e) {
}
}
}
Related
I'm using Rest controllers to populate data in my Postman. I have three tables with two of them having OneToMany relationships.
#RestController
#RequestMapping("/vehicle")
#Slf4j
public class VehicleRest
{
private VehicleService vehicleService;
public VehicleRest(VehicleService vehicleService) {
this.vehicleService = vehicleService;
}
#GetMapping("/") // get statement
public Iterable<Vehicle> getAllVehicle() {
return vehicleService.findAll();
}
#PostMapping("/") //create new
public ResponseEntity<Vehicle> addVehicle(#RequestBody Vehicle vehicle) {
return new ResponseEntity<>(
vehicleService.saveVehicle(vehicle),
HttpStatus.CREATED
);
}
#PutMapping("/") //update statement
public ResponseEntity<Vehicle> updateVehicle(#RequestBody Vehicle vehicle) {
return new ResponseEntity<>(
vehicleService.saveVehicle(vehicle),
HttpStatus.ACCEPTED
);
}
#PatchMapping("/{id}") // passes in the variable as an id, partial update
public ResponseEntity<?> partialUpdateDynamic(
#RequestBody Map<String, Object> updates,
#PathVariable Integer id) {
return new ResponseEntity<>(
vehicleService.patchVehicle(updates, id),
HttpStatus.ACCEPTED
);
}
#DeleteMapping("/{id}") //cut out, didnt like her anyways
public void deleteVehicleById(#PathVariable Integer id){
vehicleService.deleteVehicleById(id);
}
#GetMapping() // find by specific paramaters
public ResponseEntity<Vehicle> findlicensePlateOrId(#RequestParam(required = false) Integer id,
#RequestParam(required = false) String licensePlate) {
if (licensePlate != null && !licensePlate.isEmpty()) {
return ResponseEntity.ok(vehicleService.findBylicensePlateOrId(null, id));
}
return null;
}
#GetMapping("{id}") // find by id only
public ResponseEntity<Vehicle> findByIdInPath(#PathVariable Integer id) {
return ResponseEntity.ok(vehicleService.findBylicensePlateOrId(null, id));
}
#GetMapping("/test") // test to make sure we be connected and shid
public ResponseEntity <String> welcomeDevs() { // carrots hold what will be in ok
return ResponseEntity.ok("Welcome back devs");
}
}
I have a SeedData class for creating the initial data in the tables.
#Component
public class SeedData implements CommandLineRunner {
public SeedData(VehicleService vehicleService, VehicleMakeService vehicleMakeService, VehicleModelService vehicleModelSerivce) {
this.vehicleService = vehicleService;
this.vehicleMakeService = vehicleMakeService;
this.vehicleModelService = vehicleModelSerivce;
}
private final VehicleModelService vehicleModelService;
private final VehicleService vehicleService;
private final VehicleMakeService vehicleMakeService;
#Override
public void run(String... args) throws Exception {
// Setting Vehicle fields
Vehicle vehicle1 = new Vehicle();
vehicle1.setYear(2013);
vehicle1.setLicensePlate("Z15304");
vehicle1.setVin("0ALJ48FKJH12394");
vehicle1.setColor("Black");
vehicleService.saveVehicle(vehicle1);
Vehicle vehicle2 = new Vehicle();
vehicle2.setYear(2014);
vehicle2.setLicensePlate("L09A12");
vehicle2.setVin("HD18SJNSODPF891");
vehicle2.setColor("Blue");
vehicleService.saveVehicle(vehicle2);
Vehicle vehicle3 = new Vehicle();
vehicle3.setYear(2015);
vehicle3.setLicensePlate("A9UJF4");
vehicle3.setVin("KS843L0987DK147");
vehicle3.setColor("Purple");
vehicleService.saveVehicle(vehicle3);
VehicleMake vehicleMake1 = new VehicleMake();
vehicleMake1.setYear(2013);
vehicleMake1.setVehicleMakeName("Ford");
vehicleMakeService.saveVehicleMake(vehicleMake1);
VehicleMake vehicleMake2 = new VehicleMake();
vehicleMake2.setYear(2014);
vehicleMake2.setVehicleMakeName("Chevrolet");
vehicleMakeService.saveVehicleMake(vehicleMake2);
VehicleMake vehicleMake3 = new VehicleMake();
vehicleMake3.setYear(2016);
vehicleMake3.setVehicleMakeName("Mitsubishi");
vehicleMakeService.saveVehicleMake(vehicleMake3);
VehicleModel vehicleModel1 = new VehicleModel();
vehicleModel1.setVehicleModelName("Raptor");
vehicleModelService.saveVehicleModel(vehicleModel1);
VehicleModel vehicleModel2 = new VehicleModel();
vehicleModel2.setVehicleModelName("Impala");
vehicleModelService.saveVehicleModel(vehicleModel2);
VehicleModel vehicleModel3 = new VehicleModel();
vehicleModel3.setVehicleModelName("Lancer");
vehicleModelService.saveVehicleModel(vehicleModel3);
List<Vehicle> vList1 = new ArrayList<>();
vList1.add(vehicle1);
List<Vehicle> vList2 = new ArrayList<>();
vList2.add(vehicle2);
List<Vehicle> vList3 = new ArrayList<>();
vList3.add(vehicle3);
vehicleModel1.setVehicle(vList1);
vehicleModel2.setVehicle(vList2);
vehicleModel3.setVehicle(vList3);
vehicleModelService.saveVehicleModel(vehicleModel1);
vehicleModelService.saveVehicleModel(vehicleModel2);
vehicleModelService.saveVehicleModel(vehicleModel3);
List<VehicleModel> vmList1 = new ArrayList<>();
vmList1.add(vehicleModel1);
List<VehicleModel> vmList2 = new ArrayList<>();
vmList2.add(vehicleModel2);
List<VehicleModel> vmList3 = new ArrayList<>();
vmList3.add(vehicleModel3);
vehicleMake1.setVehicleModel(vmList1);
vehicleMake2.setVehicleModel(vmList2);
vehicleMake3.setVehicleModel(vmList3);
vehicleMakeService.saveVehicleMake(vehicleMake1);
vehicleMakeService.saveVehicleMake(vehicleMake2);
vehicleMakeService.saveVehicleMake(vehicleMake3);
}
}
Here is a class with a relationship.
#Entity
#Data
public class VehicleMake {
#Id
#GeneratedValue(strategy = IDENTITY)
private Integer id;
#Column(unique = true)
private String vehicleMakeName;
private Integer year;
#OneToMany(cascade = CascadeType.ALL)
private List<VehicleModel> vehicleModel;
}
The issue that I am having is in the Crud functions. In the parent table I am able to manipulate the data without an issue. I am unable to update or delete anything in the child tables without getting the below exception:
2022-09-28 12:43:12.801 ERROR 570807 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : Referential integrity constraint violation: "FK9STO574BS4V9KE5O2BS5JHIJ5: PUBLIC.VEHICLE_MAKE_VEHICLE_MODEL FOREIGN KEY(VEHICLE_MODEL_ID) REFERENCES PUBLIC.VEHICLE_MODEL(ID) (1)"; SQL statement:
This is my service interface and implementation with Overrides:
public interface VehicleMakeService {
VehicleMake findById(Integer id);
Iterable<VehicleMake> findAll();
VehicleMake patchVehicleMake(Map<String, Object> updates, Integer id);
VehicleMake saveVehicleMake(VehicleMake vehicleMake);
VehicleMake updateVehicleMake(VehicleMake vehicleMake);
void deleteVehicleMake(Integer id);
}
#Service
public class VehicleMakeServiceImpl implements VehicleMakeService {
private VehicleMakeRepo vehicleMakeRepo;
public VehicleMakeServiceImpl(VehicleMakeRepo vehicleMakeRepo) {
this.vehicleMakeRepo = vehicleMakeRepo;
}
public Iterable <VehicleMake> findAll() {
return vehicleMakeRepo.findAll();
}
#Override
public VehicleMake patchVehicleMake(Map<String, Object> updates, Integer id) throws FieldNotFoundException {
//find the product by id or throw and exception
VehicleMake vehicleMakeToPatch = vehicleMakeRepo.findById(id)
.orElseThrow(() -> new VehicleMakeNotFoundException(String.valueOf(id)));
// iterate over the map of fields to update
updates.forEach((k, o) -> {
System.out.println(k + ":" + o);
// use reflection to get the setter/accessor for field name
try {
Field nameField = vehicleMakeToPatch.getClass().getDeclaredField(k);
// set the field to the value
nameField.setAccessible(true);
nameField.set(vehicleMakeToPatch, o);
// handle exceptions
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new FieldNotFoundException(k);
}
});
return vehicleMakeRepo.save(vehicleMakeToPatch);
}
#Override
public VehicleMake saveVehicleMake(VehicleMake vehicleMake) {
return vehicleMakeRepo.save(vehicleMake);
}
#Override
public VehicleMake updateVehicleMake(VehicleMake vehicleMake) {
return vehicleMakeRepo.save(vehicleMake);
}
#Override
public void deleteVehicleMake(Integer id) {
vehicleMakeRepo.deleteById(id);
}
#Override
public VehicleMake findById(Integer id) {
return (VehicleMake) vehicleMakeRepo.findById(id)
.orElseThrow(() -> new VehicleMakeNotFoundException(String.valueOf(id)));
}
}
I understand that there is foreign key constraint that is happening, I do not understand the solution to being able to manipulate the child table without having that constraint trigger. I'm probably having some tunnel vision with it at this point.
As part of sequence generation for non-primary key, I am using #GeneratorType on entity field. In generator class I require to know a field on that it has been called. Help is appreciated.
#Entity(name = "student")
public class Student {
#GeneratorType(type = IdGenerator.class, when = GenerationTime.INSERT)
private Integer secId;
}
public class IdGenerator implements ValueGenerator<Integer>{
#Override
public Integer generateValue(Session session, Object owner) {
// I want secId here
}
}
You can try to use something like this:
public interface EntityId {
Integer getId();
}
#Entity(name = "student")
public class Student implements EntityId {
#GeneratorType(type = IdGenerator.class, when = GenerationTime.INSERT)
private Integer secId;
#Override
public Integer getId() {
return secId;
}
// ...
}
#Entity
public class OtherEntity implements EntityId {
// ...
}
public class IdGenerator implements ValueGenerator<Integer> {
#Override
public Integer generateValue(Session session, Object owner) {
if (!(owner instanceof EntityId)) {
throw new IllegalArgumentException("IdGenerator can be used only with entities that implement EntityId interface");
}
// I want secId here
Integer id = ((EntityId) owner).getId();
}
}
If you use Java 14 or higher you can use pattern matching for instanceof as more elegant construction.
public class IdGenerator implements ValueGenerator<Integer> {
#Override
public Integer generateValue(Session session, Object owner) {
if (owner instanceof EntityId entityId) {
// I want secId here
Integer id = entityId.getId();
// ...
} else {
throw new IllegalArgumentException("IdGenerator can be used only with entities that implement EntityId interface");
}
}
}
My question is actually a spin-off of this question as seen here... so it might help to check that thread before proceeding.
In my Spring Boot project, I have two entities Sender and Recipient which represent a Customer and pretty much have the same fields, so I make them extend the base class Customer;
Customer base class;
#MappedSuperclass
public class Customer extends AuditableEntity {
#Column(name = "firstname")
private String firstname;
#Transient
private CustomerRole role;
public Customer(CustomerRole role) {
this.role = role;
}
//other fields & corresponding getters and setters
}
Sender domain object;
#Entity
#Table(name = "senders")
public class Sender extends Customer {
public Sender(){
super.setRole(CustomerRole.SENDER);
}
}
Recipient domain object;
#Entity
#Table(name = "recipients")
public class Recipient extends Customer {
public Recipient(){
super.setRole(CustomerRole.RECIPIENT);
}
}
NOTE - Sender and Recipient are exactly alike except for their roles. These can be easily stored in a single customers Table by making the Customer base class an entity itself, but I intentionally separate the entities this way because I have an obligation to persist each customer type in separate database tables.
Now I have one form in a view that collects details of both Sender & Recipient, so for example to collect the firstname, I had to name the form fields differently as follows;
Sender section of the form;
<input type="text" id="senderFirstname" name="senderFirstname" value="$!sender.firstname">
Recipient section of the form;
<input type="text" id="recipientFirstname" name="recipientFirstname" value="$!recipient.firstname">
But the fields available for a customer are so many that I'm looking for a way to map them to a pojo by means of an annotation as asked in this question here. However, the solutions provided there would mean that I have to create separate proxies for both domain objects and annotate the fields accordingly e.g
public class SenderProxy {
#ParamName("senderFirstname")
private String firstname;
#ParamName("senderLastname")
private String lastname;
//...
}
public class RecipientProxy {
#ParamName("recipientFirstname")
private String firstname;
#ParamName("recipientLastname")
private String lastname;
//...
}
So I got very curious and was wondering, is there a way to map this Proxies to more than one #ParamName such that the base class for example can just be annotated as follows?;
#MappedSuperclass
public class Customer extends AuditableEntity {
#Column(name = "firstname")
#ParamNames({"senderFirstname", "recipientFirstname"})
private String firstname;
#Column(name = "lastname")
#ParamNames({"senderLastname", "recipientLastname"})
private String lastname;
#Transient
private CustomerRole role;
public Customer(CustomerRole role) {
this.role = role;
}
//other fields & corresponding getters and setters
}
And then perhaps find a way to select value of fields based on annotation??
A suggestion from Zhang Jie like ExtendedBeanInfo
so i do it this way
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Alias {
String[] value();
}
public class AliasedBeanInfoFactory implements BeanInfoFactory, Ordered {
#Override
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
return supports(beanClass) ? new AliasedBeanInfo(Introspector.getBeanInfo(beanClass)) : null;
}
private boolean supports(Class<?> beanClass) {
Class<?> targetClass = beanClass;
do {
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Alias.class)) {
return true;
}
}
targetClass = targetClass.getSuperclass();
} while (targetClass != null && targetClass != Object.class);
return false;
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 100;
}
}
public class AliasedBeanInfo implements BeanInfo {
private static final Logger LOGGER = LoggerFactory.getLogger(AliasedBeanInfo.class);
private final BeanInfo delegate;
private final Set<PropertyDescriptor> propertyDescriptors = new TreeSet<>(new PropertyDescriptorComparator());
AliasedBeanInfo(BeanInfo delegate) {
this.delegate = delegate;
this.propertyDescriptors.addAll(Arrays.asList(delegate.getPropertyDescriptors()));
Class<?> beanClass = delegate.getBeanDescriptor().getBeanClass();
for (Field field : findAliasedFields(beanClass)) {
Optional<PropertyDescriptor> optional = findExistingPropertyDescriptor(field.getName(), field.getType());
if (!optional.isPresent()) {
LOGGER.warn("there is no PropertyDescriptor for field[{}]", field);
continue;
}
Alias alias = field.getAnnotation(Alias.class);
addAliasPropertyDescriptor(alias.value(), optional.get());
}
}
private List<Field> findAliasedFields(Class<?> beanClass) {
List<Field> fields = new ArrayList<>();
ReflectionUtils.doWithFields(beanClass,
fields::add,
field -> field.isAnnotationPresent(Alias.class));
return fields;
}
private Optional<PropertyDescriptor> findExistingPropertyDescriptor(String propertyName, Class<?> propertyType) {
return propertyDescriptors
.stream()
.filter(pd -> pd.getName().equals(propertyName) && pd.getPropertyType().equals(propertyType))
.findAny();
}
private void addAliasPropertyDescriptor(String[] values, PropertyDescriptor propertyDescriptor) {
for (String value : values) {
if (!value.isEmpty()) {
try {
this.propertyDescriptors.add(new PropertyDescriptor(
value, propertyDescriptor.getReadMethod(), propertyDescriptor.getWriteMethod()));
} catch (IntrospectionException e) {
LOGGER.error("add field[{}] alias[{}] property descriptor error", propertyDescriptor.getName(),
value, e);
}
}
}
}
#Override
public BeanDescriptor getBeanDescriptor() {
return this.delegate.getBeanDescriptor();
}
#Override
public EventSetDescriptor[] getEventSetDescriptors() {
return this.delegate.getEventSetDescriptors();
}
#Override
public int getDefaultEventIndex() {
return this.delegate.getDefaultEventIndex();
}
#Override
public PropertyDescriptor[] getPropertyDescriptors() {
return this.propertyDescriptors.toArray(new PropertyDescriptor[0]);
}
#Override
public int getDefaultPropertyIndex() {
return this.delegate.getDefaultPropertyIndex();
}
#Override
public MethodDescriptor[] getMethodDescriptors() {
return this.delegate.getMethodDescriptors();
}
#Override
public BeanInfo[] getAdditionalBeanInfo() {
return this.delegate.getAdditionalBeanInfo();
}
#Override
public Image getIcon(int iconKind) {
return this.delegate.getIcon(iconKind);
}
static class PropertyDescriptorComparator implements Comparator<PropertyDescriptor> {
#Override
public int compare(PropertyDescriptor desc1, PropertyDescriptor desc2) {
String left = desc1.getName();
String right = desc2.getName();
for (int i = 0; i < left.length(); i++) {
if (right.length() == i) {
return 1;
}
int result = left.getBytes()[i] - right.getBytes()[i];
if (result != 0) {
return result;
}
}
return left.length() - right.length();
}
}
}
I'm trying to render like/dislike buttons using JPA and JSF
#Entity
public class APost implements Serializable {
...
#ManyToMany(fetch = FetchType.EAGER)
protected Collection<User> likes;
#ManyToMany(fetch = FetchType.EAGER)
protected Collection<User> dislikes;
#Transient
public Integer getLikesNumber() {
if (likes == null) {
return 0;
}
return likes.size();
}
public Collection<User> getLikes() {
return likes;
}
public void setLikes(Collection<User> likes) {
this.likes = likes;
}
public void addLikes(User user) {
if (likes == null) {
likes = new HashSet<>();
}
likes.add(user);
}
public void removeLikes(User user) {
if (likes != null) {
likes.remove(user);
}
}
#Transient
public Integer getDislikesNumber() {
if (dislikes == null) {
return 0;
}
return dislikes.size();
}
public Collection<User> getDislikes() {
return dislikes;
}
public void setDislikes(Collection<User> dislikes) {
this.dislikes = dislikes;
}
public void addDislikes(User user) {
if (dislikes == null) {
dislikes = new HashSet<>();
}
dislikes.add(user);
}
public void removeDislikes(User user) {
if (dislikes != null) {
dislikes.remove(user);
}
}
}
The User Class :
#Entity
public class User implements Serializable {
...
#Id
private String email;
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "likes")
protected Collection<APost> likes;
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "dislikes")
protected Collection<APost> dislikes;
public Collection<APost> getLikes() {
return likes;
}
public void addLikes(APost post) {
if (likes == null) {
likes = new HashSet<>();
}
likes.add(post);
}
public void removeLikes(APost post) {
if (likes != null) {
likes.remove(post);
}
}
public Collection<APost> getDislikes() {
return dislikes;
}
public void addDislikes(APost post) {
if (dislikes == null) {
dislikes = new HashSet<>();
}
dislikes.add(post);
}
public void removeDislikes(APost post) {
if (dislikes != null) {
dislikes.remove(post);
}
}
#Override
public boolean equals(Object obj) {
if (obj == null)
return false;
User uObj = (User) obj;
return getEmail().equals(uObj.getEmail());
}
}
Facelet : post.xhtml
...
<h:commandLink
action="#{bean.addLike(post.id)}"
<h:graphicImage library="images" name="thumb-up-24x31.png"></h:graphicImage>
</h:commandLink>
...
Bean.java
#ManagedBean
#ConversationScoped
public class OnePostManager implements Serializable {
private static final long serialVersionUID = 1L;
#EJB
private IPostFacade postFacade;
#EJB
private IUserFacade userFacade;
#Inject
private LoginManager loginManager;
...
public String addLike(Long postId) {
if (loginManager.getConnected().equals(false)) {
return "login?redirect=post&&faces-redirect=true";
}
if (postId != null) {
APost post = postFacade.find(postId);
User user = userFacade.find(loginManager.getEmail());
post.addLikes(user);
postFacade.edit(post);
}
return null;
}
}
Now, when I click on the "like" button, I got an exception :
javax.faces.el.EvaluationException: javax.ejb.EJBException: Transaction aborted
...
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507- 3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`medianovens`.`APOST_USER`, CONSTRAINT `FK_APOST_USER_likes_EMAIL` FOREIGN KEY (`likes_EMAIL`) REFERENCES `USER` (`EMAIL`))
Error Code: 1452
Call: INSERT INTO APOST_USER (dislikes_EMAIL, dislikes_ID) VALUES (?, ?)
bind => [2 parameters bound]
Query: DataModifyQuery(name="dislikes" sql="INSERT INTO APOST_USER (dislikes_EMAIL, dislikes_ID) VALUES (?, ?)")
...
When I look at my database, I've got one table called APOST_USER with 4 columns : dislikes_EMAIL, dislikes_ID, likes_EMAIL, likes_ID
So I suppose that when it tries to add an entry that defines which post a user likes, it tries to fill likes_EMAIL and likes_ID but also expects some values for dislikes_EMAIL and dislikes_ID ...
How can I solve this ?
Note :
When I remove all code concerning dislike functions, the code works OK (My table APOST_USER only has 2 columns, likes_EMAIL and likes_ID) and an entry can be added, but everything goes wrong if I add all code regarding dislike function.
It sounds like your JPA provider is trying to be smart and combine the two references to APost into one. What you should probably do is look into the #JoinTable annotation, which would allow you to definitively specify different tables should be used for your Like and Dislike features.
http://blog.jbaysolutions.com/2012/12/17/jpa-2-relationships-many-to-many/ shows how this is used in practice.
Here's what I think it should look like to fix your mapping problem:
#Entity
public class APost implements Serializable {
...
#JoinTable(name="post_likes")
#ManyToMany(fetch = FetchType.EAGER)
protected Collection<User> likes;
#JoinTable(name="post_dislikes")
#ManyToMany(fetch = FetchType.EAGER)
protected Collection<User> dislikes;
You need to assign both sides of the relationship:
post.addLikes(user);
user.addLikes(post);
I am trying out some EJB3 in Action examples using Glassfish4 (EclipseLink) + JavaDB. So I have the below relationship
#Entity
#Table(name = "ITEMS")
public class Item implements Serializable {
private static final long serialVersionUID = 1L;
private Long itemId;
...
private List<Bid> bids= new ArrayList<>();
#Id
#Column(name="ITEM_ID")
public Long getItemId() {
return itemId;
}
public void setItemId(Long itemId) {
this.itemId = itemId;
}
#OneToMany(mappedBy="item",fetch=FetchType.EAGER)
#JoinColumn(name="BID_ITEM_ID",referencedColumnName="ITEM_ID")
public List<Bid> getBids() {
return bids;
}
public void setBids(List<Bid> bids) {
this.bids = bids;
}
}
#Entity
#Table(name="BIDS")
public class Bid implements Serializable{
private static final long serialVersionUID = 1L;
...
private Item item;
...
#Id
#Column(name="BID_ID")
public Long getBidId() {
return bidId;
}
public void setBidId(Long bidId) {
this.bidId = bidId;
}
#ManyToOne
#JoinColumn(name="BID_ITEM_ID",referencedColumnName="ITEM_ID")
public Item getItem() {
return item;
}
public void setItem(Item item) {
this.item = item;
}
...
}
Now when fetching an Item like
#Override
public List<Bid> getBidsForItem(long itemId) {
Item item = em.find(Item.class, itemId); // em -> Entity manager
return item.getBids();
}
the item.getBids() returns an empty list (size = 0, not null). What changes should be done to get Bids for the given Item?
EDIT:
After enabling query logging as suggested in the comments
<property name="eclipselink.logging.level.sql" value="FINE"/>
<property name="eclipselink.logging.parameters" value="true"/>
I notice that Queries are listed for insert statements but NO query is listed corresponding to em.find(Item.class, itemId).
EDIT 2 (ANSWER):
The problem was in my addBids() stateless bean function to which I was passing an Item object. This meant the Item Object is never in persistent context. The right way is to
pass the itemId
find the Item entity with entity manager find() method. This ensures that the Item object is in persistence context.
add bid object to item and item to bid
call entity manager persist() on Bid.
Corrected addBids() method:
public Bid addBids(Date bidDate, Double bidPrice, long itemId, String bidder) {
Item item = em.find(Item.class, itemId);
Bid bid = new Bid(bidDate, bidPrice, item, bidder);
item.getBids().add(bid);
em.persist(bid);
return bid;
}
Thanks to #Chris for pointing out.
Try instantiating a ArrayList<Bid> and assigning it to the List<Bid> declaration.
#OneToMany(mappedBy="item")
protected List<Bid> bids = new ArrayList<Bid>();
Try this :-
public class Item{
private List<Bid> bids= new ArrayList<>();
public void setBids(List<Bid> bids) {
for (Bid bid : bids) {
bid.setItem(this);
}
this.bids = bids;
}
}
Here you are freeing the client to make the relationship. Do the otherway round in Bid class also. But make sure you won't end up with infinite to and fro method calls.
And its a good approach to provide an add and remove method.
like :- public class Item{
private List<Bid> bids= new ArrayList<>();
public void addBid(Bid bid) {
bid.setItem(this);
this.bids.add(bids);
}
}