0

I would like to apologize in advance if this question has been answered before.

Please note that the project is using Spring Boot, Jackson and Hibernate.

Anyway, I forked a project by Jasenko Hadziomeragic. I wanted to know how he was able to save an object, in Jasenko's project, the Product object. In this project, the Product object looks like this (please note that I removed parts of the code to focus on the foreign key) :

@JoinColumn(name = "member_id")
@NotNull
private Long member_id;

I can save a new record by using this JSON:

{
  "name" : "Product 2",
  "member_id" : "1"
}

This works but from what I have scoured in the internet, it's better to use this instead:

public class Product {

@ManyToOne()
@JoinColumn(name="member_id")
private Member member;

/* A getter/setter for the memeber id*/
public Long getMember_id() {
    return this.member.getId();
}

public void setMember_id(Long member_id) {
    this.member.setId(member_id);
}
}

I tried to save a new record with the same json above, I get this error:

java.lang.NullPointerException: null
at com.jersey.representations.Product.setMember_id(Product.java:100)

Here's a link to my github that contains modified codes.

I also checked this SO question that is similar to my concern. But what it does it first it sends a select query first to populate the Member object. But that means before I save a record, I have to request another select statement. It's a bit of an overhead, isn't it?

UPDATE I added this code in the constructor:

public Product() {
    this.member = new Member();
}

I did fix my issue but is this the correct way of dealing with my concern?

UPDATE 2

What I am trying to avoid is sending this JSON:

[
    {
        "id": 1,
        "name": "Derp",
        "currency": "599",
        "regularPrice": 1203,
        "discountPrice": 59,
        "member": {
            "id": 1,
            "firstName": "Paul",
            "lastName": "Andrews",
            "email": "myemail@email.com"
        }
    },
    {
        "id": 2,
        "name": "Another product ",
       "currency": "909",
        "regularPrice": 1203,
        "discountPrice": 59,
        "member": {
            "id": 1,
            "firstName": "Paul",
            "lastName": "Andrews",
            "email": "myemail@email.com"
        }
    },
    {
        "id": 3,
        "name": "Another product 1",
        "currency": "909",
        "regularPrice": 1203,
        "discountPrice": 59,
        "member": {
            "id": 1,
            "firstName": "Paul",
            "lastName": "Andrews",
            "email": "myemail@email.com"
        }
    }
]

As you can see, the member keeps repeating whereas I can just send the memberId.

Another link to a similar question I posted

Community
  • 1
  • 1
Francis Zabala
  • 1,037
  • 2
  • 11
  • 30
  • You have probably NPE because your `member` field to which you are trying to set `member_id` value is not initialized. Firstly you have to create `member` and then assign him id. – wawek Oct 23 '15 at 06:15
  • But what if the member already exists? Won't that create another member record? – Francis Zabala Oct 23 '15 at 06:16
  • So you have to find out if it already exists. IMO the way you do it is wrong. Why do you want to have method which is setting id for the member in Product class? In Product class you should set the whole `Member` object or you should have `Long memberId` field and set your `member_id` value to this field. – wawek Oct 23 '15 at 06:20
  • I'll update my question to answer your concerns. – Francis Zabala Oct 23 '15 at 06:24
  • The better way would be to either create `new Member()` if it doesn't exist or assign existing member from database something like `dao.findMemberById(memberId);` – wawek Oct 23 '15 at 06:26
  • But that's a severe overhead. What if member table is so huge that a query for a single row returns in 5 seconds. But you have to save 2000 products with different memberIds. That is going to take a lot of time where as simple insert statement is faster. – Francis Zabala Oct 23 '15 at 06:31
  • @FrancisZabala You can use a fake Member with only an id field setted. And set this member to Product because of you need a Member foreign key only. You don't need save members, only products. – v.ladynev Oct 23 '15 at 09:38
  • @v.ladynev hello again. Yes, I tried that by adding new Member() in the Product constructor. Is that the best practice or am I doing something wrong? – Francis Zabala Oct 23 '15 at 10:05
  • @FrancisZabala Hello :) No. It is a bad one. [See this](https://github.com/v-ladynev/fluent-hibernate/blob/master/fluent-hibernate-examples/simply-console/src/main/java/com/github/fluent/hibernate/example/mysql/persistent/User.java#L73) for adding products to Member. If you want to deal without adding products to Member: You need an id of Member, create Member, set id, and set Member to Product...than save Product. – v.ladynev Oct 23 '15 at 10:12

2 Answers2

0

Please, try to learn Hibernate using a simply console application with Model and Product persistents. You don't need @JoinColumn for products but @OneToMany with mappedBy property. You can refer User as an analogue of Member and UserFriend as Product. In every Product you need to have foreign key to User — add products to member this way. For experiments you can use this or this.

v.ladynev
  • 19,275
  • 8
  • 46
  • 67
0

Ok so I finally got to make it work. I don't know where to pinpoint but I can definitely say that it's not JSON marshalling by Jackson issue.

Here's my solution:

First, I can't find a solution but to add this to class that has the foreign key:

@Entity
public class Product {
    public Product() {
        this.member = new Member();
    }

    @ManyToOne()
    @JoinColumn(name="blargh")
    private Member member;
    /** Jackson can't instantiate and object automatically so you have to help it **/
    /** By creating the object right after Product is initialzed by some other code **/
    public Long getMobileNum() {
        return this.member.getMobileNum();
    }

    public void setMobileNum(Long mobileNum) {
        this.member.setMobileNum(mobileNum);
    }
}

Next step to look for it the Id annotation in the other class.

@Entity
public class Member {

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Id
    private Long mobileNum;
}

I intentionally made the annotations wrong to prove a point. In the Product class, it's foreign key that links it to the Member class is mobileNum. If mobileNum or any foreign key is annotated with @Id, Hibernate will bind it properly and won't have any issues saving the object.

But if the foreign key is not annotated with @Id, Hibernate (even if Jackson has properly mapped the JSON to the object), it will not bind it properly resulting in null values. Google search keyword: object references an unsaved transient instance

Adding Cascade is not a good solution in my opinion as it just creates more records in the Member class. It will keep on creating EVEN if the record already exists! (Trying putting a unique constraint on mobileNum and remove the @Id annotation.

Overall, this week in Hibernate has really burned me out. If you're a developer who had experience in optimizing an existing SQL and just want to turn it into code, stay away from Hibernate. Use MyBatis, even if it has a lot of boilerplate, it's much more satisfying to create your SQL by hand than by Hibernate.

Francis Zabala
  • 1,037
  • 2
  • 11
  • 30