4

I saw several similar questions but non with an answer that worked for me. I am working on converting an existing project over to use Hibernate. I am using Mysql for the database. I have a class like this:

@Entity
@Table(name = "user")
public class User {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "user_id")
   private Long userId;

   @ManyToOne
   @JoinColumn(name = "group_id_user")
   private Group group;

   @Column(name = "name")
   private String name;

   ...
   // getters and setters....   
}

and a class like this:

@Entity
@Table(name = "group")
public class Group {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "group_id")
   private Long groupId;

   @Column(name="group_name")
   private String groupName;

   ...
   // getters and setters....   
}

When I fetch a User item from the database I convert it to JSON and end up with a result in this format:

{
    "user": {
        "userId": 1,
        "group": {
            "groupId": 3,
            "groupName": "Group Three",

        },
        "name": "John Doe",
     }
}

In my case, the Group object is actually quite a bit bigger than in this example and also contains references to another table as well (i.e. Group has a SomeOtherClass object). I want to end up with something that looks like the following:

{
    "user": {
        "userId": 1,
        "groupId": 3,
        "name": "John Doe",
     }
}

Is there a way to just get the groupId in my JSON response instead of the entire Group object?

I have tried adding FetchType.Lazy to my User class

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "group_id_user")
private Group group;

but when I try to convert the User object to Json I get the following error:

error: Errorjava.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: org.hibernate.proxy.HibernateProxy. Forgot to register a type adapter?

I found this answer which gets rid of the error, but then the JSON object is exactly the same as it was before I added the FetchType.LAZY. It looks like somehow converting to JSON adds in the values that the FetchType.LAZY was ignoring.

I am using converting to JSON as follows:

GsonBuilder b = new GsonBuilder();
Gson gson = b.create();
//b.registerTypeAdapterFactory(HibernateProxyTypeAdapter.FACTORY);  

com.google.gson.JsonArray jsonArray = gson.toJsonTree(out).getAsJsonArray();
Gerald Murphy
  • 307
  • 1
  • 3
  • 17
  • see https://stackoverflow.com/a/7811253/180100 –  Sep 07 '17 at 17:05
  • 1
    The answer is the same as the other one you asked: decouple the output you send as a response to your REST API from the entities, and everything will be simpler. I.e. don't serialize your entities. Serialize objects that represent what your API is designed to send as a response. – JB Nizet Sep 07 '17 at 17:06
  • Excuse me.Are you talking about Http Response? – Tsakiroglou Fotis Sep 07 '17 at 17:21
  • @JBNizet If I understand correctly you are suggesting that instead of using Gson to convert an entity to Json I should just create a new empty Json object and add all the data from the entity that I need to the Json object and leave out the parts that I don't need (in this example, leave out the Group Object and add userId, groupId, and name). This might work for fetching a single small entity, but for fetching a list of more complex entities this process could become more complicated. It seems like there should be a way to just convert an entity as is but replace sub-entities with their ids. – Gerald Murphy Sep 07 '17 at 17:29
  • @TsakiroglouFotis I am sending the data that I convert to JSON back to the client in an Http Response. – Gerald Murphy Sep 07 '17 at 17:30
  • 1
    No. Gson is an object mapper. Create classes (DTOs) which represent the output of your API, transform your entities to instances of these DTO classes, and use Gson to serialize the DTOs to JSON. Good REST frameworks do the last part automatically for you. – JB Nizet Sep 07 '17 at 17:33
  • @RC It seems like the responses on the link you posted mostly focus on how to exclude a field from the json object. I do not want to necessarily just exclude "group" from the response, I also want to include the "groupId". – Gerald Murphy Sep 07 '17 at 17:34
  • @JBNizet So in this case I would have my User class that represents the user table in the database and then create a new class, say UserItem that represents what I want to send back in my response. Then Convert the User object to a UserItem object and then covert the UserItem to a json response? – Gerald Murphy Sep 07 '17 at 17:54
  • Yes, you got it. – JB Nizet Sep 07 '17 at 17:58

2 Answers2

4

Correct me if I wrong, as I'm new to hibernate. Based on my experience so far, Yes it is possible to send only object ID instead of sending the whole object details in response. By using @JsonIdentityReference and @JsonIdentityInfo find documentation here

Below is the sample code worked for me:

Group Class

@Entity
@Table(name = "group")
public class Group {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "group_id")
   private Long groupId;

   @Column(name="group_name")
   private String groupName;

   ...
   // getters and setters....   
}

User Class

@Entity
@Table(name = "user")
public class User {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "user_id")
   private Long userId;

   @ManyToOne
   @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "group_id")
   @JsonIdentityReference(alwaysAsId = true)
   @JoinColumn(name = "group_id_user")
   private Group group; 
   ...
   // getters and setters....   
}

Result is

{ "userId": 1, "group": 1, "name": "Sample User" }

I used below code to convert JSON

ObjectMapper mapper = new ObjectMapper();
try {
      json = mapper.writeValue(user);
} catch (JsonProcessingException e) {
      e.printStackTrace();
}

I found the solution for my issue here

1

Option 1: (from @JBNizet's comment)

Create classes (DTOs) which represent the output of your API, transform your entities to instances of these DTO classes, and use Gson to serialize the DTOs to JSON. Good REST frameworks do the last part automatically for you.

For this example you could create a UserItem class that looked like this:

public class UserItem {

    private Long userId;
    private Long groupId;
    private String name;
   ...
   // getters and setters....   
}

Then you would need a method that converts the User entity to a UserItem:

public UserItem UserToUserItem(User user) {
    UserItem userItem = new UserItem();
    userItem.setUserId(user.getUserId());
    userItem.setGroupId(user.getGroup().getGroupId());
    userItem.setName(user.getName());

    return userItem;
}

The use the userItem in your response.


Option 2:

UPDATE: Updated option 2 to something that works

If you don't want to write DTOs for each entity class because (like this example) the only field that needs changed is the Group object to the groupId you can create an ExclusionStrategy and use the @Expose annotation as follows:

First create a class that implements ExclusionStrategy

public class SerializeExclusionStrat implements ExclusionStrategy {

    @Override
    public boolean shouldSkipField(FieldAttributes fieldAttributes) {
        final Expose expose = fieldAttributes.getAnnotation(Expose.class);
        return expose != null && !expose.serialize();
    }

    @Override
    public boolean shouldSkipClass(Class<?> aClass) {
        return false;
    }
}


@Entity
@Table(name = "user")
public class User {

Then add the @Expose annotation to User class. Adding the additional field groupId will allow the groupId to be returned in your queries and in your json responses. Just make sure to mark groupId with updatable=false and insertable=false.

@Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "user_id")
   private Long userId;

   @ManyToOne
   @JoinColumn(name = "group_id_user")
   @Expose(serialize = false)
   private Group group;

   @Column(name = "group_id_user", updatable = false, insertable = false)
   private Long groupId;     

   @Column(name = "name")
   private String name;

   ...
   // getters and setters....   
}

Then set the Exclusion strategy when creating the Gson:

GsonBuilder b = new GsonBuilder();
b.serializeNulls();
b.setExclusionStrategies(new SerializeExclusionStrat());
Gson gson = b.create();
Gerald Murphy
  • 307
  • 1
  • 3
  • 17
  • The JPA spec says: *All non-transient instance variables that are not annotated with the Transient annotation are persistent.*. So, marking a field transient will also prevent it from being saved in the database. – JB Nizet Sep 07 '17 at 21:12
  • @JBNizet hmm...looks like you are right. I was able to fetch data from the database correctly, but now that I check adding it looks like the "group_id_user" value got set to null. There has got to be a way to do this without having to create DTOs for each entity that has a foreign key... – Gerald Murphy Sep 07 '17 at 21:47
  • I have edited the answer to show a different way that does work for both adding to and fetching from the database. – Gerald Murphy Sep 07 '17 at 22:21