0

We have a class User, which has a field isMigrated which is 0 by default. Now in the service layer, we are trying to update the user's status (another attribute). So these are the steps

  1. We fetch the user by his ID (Primary key).
  2. We set the new status.
  3. We call the save method in the respective repository for that particular user.

Now the problem is that the column that corresponds to isMigrated, which is currently having a value of 1 is also getting updated to 0. We have clearly not updated that attribute and so we came to a conclusion that it is the default value that is going to the DB during updation, but how is that possible?

We tried to check some other information about JPA: update only specific fields but wasn't helpful in this case. We had to go with native query to fix this problem.

Entity class

@Entity
@Table(name = "USER_TABLE")
public class User implements Serializable {

    //  other attributes

    @Column(name = "USER_STATUS")
    private int status;

    //  [1 = migrated, 0 = new (default)]
    @Column(name = "IS_MIGRATED")
    private int isMigrated = 0;

    //  getters and setters
}

Repository

@Repository
public interface UserRepository
        extends PagingAndSortingRepository<User, String>, JpaSpecificationExecutor<User> {
}

Updates regarding how the user is being fetched

The user is fetched by his ID from the request, we'll run a native query by joining two other tables by fetching only the required fields. So in the process of fetch, we are not using the isMigrated column, plus that attribute (call hierarchy) is set only when a user is created and this is a different API.

Once we fetch the user, it's reference is set to an attribute of another bean RequestInfo with a Request scope (@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)), which will be used all along the API. Now in the service layer we use the RequestInfo to get the user, then we set the new status and call the save method.

@Query(value = "select um.USER_ID,FIRST_NAME,LAST_NAME,USER_STATUS,um.MOB,EMAIL_ID,ENTITY_TYPE,PROFILE_ID,PARENT_ID,SOFT_DELETE, NATIONALITY_ID,DOB,GENDER,FATHER_NAME,QUALIFICATION,LANGUAGE_ID,DESIGNATION,CONTRACT_START_DATE, "
        + "CONTRACT_END_DATE, REGISTERED_DATE,ACTIVATED_DATE,CREATED_BY,CONTACT_EMAIL_ID,CONTACT_MSISDN,ERP_CODE, "
        + "LAST_FINANCIAL_TXN_DATE,MM_APP_VERSION,MM_APP_LAST_UPDATE_DATE,OFFICIAL_EMAIL_ID,SRC_OF_FUND,POW, JOB_NAME,JOB_STARTED,MONTHLY_INCOME, "
        + "MARITAL_STATUS,SALUTE,HIER_ID,LABEL_ID,PROFILE_PICTURE_ID,DOC_NAME,lm.ID , lm.ALERT_DATE , lm.BLOCKED_DATE , lm.IS_FORCE_PIN , lm.IS_FORCE_PASSWORD , lm.LAST_LOGIN_TIME , lm.LAST_NOTIFIED_DATE ,lm.PASSWORD , lm.PIN ,  "
        + "lm.WRONG_PIN_ATTEMPT , lm.PIN_EXPIRY_DATE , lm.WRONG_PWD_ATTEMPT , lm.PWD_EXPIRY_DATE , lm.USER_NAME, am.ID aid, am.ADDRESS_1, am.ADDRESS_2, am.ADDRESS_TYPE, am.CITY , am.PIN_CODE , am.POST_BOX , am.STATE , am.STREET_NAME,ui.ID_TYPE, ui.ID_NO,um.NOTIFICATION_SERVICE_TYPE "
        + " FROM USER_TABLE um inner join USER_LOGIN_TABLE lm ON um.USER_ID=lm.USER_ID and um.USER_ID=?1 "
        + " left outer join USER_ADDRESS_TABLE am ON um.USER_ID=am.USER_ID left outer join USER_IDENTI_CATION_DETAILS ui ON um.USER_ID = ui.USER_ID", nativeQuery = true)
List<Object[]> getUserByUserId(String userId);

Updates regarding the columns that are not fetched while getting a user - Asked by Ratul Sharker

There are a few columns that we are not fetching, like deviceId, deviceIp, etc. These are String types, so even though we are not providing a default value to these fields null will be assigned as the default value. So my doubt is why the default value is inserted into isMigrated and why the same is not inserted to the other two fields because these three are not fetched in the above query right?

Arun Sudhakaran
  • 2,167
  • 4
  • 27
  • 52

1 Answers1

1

The way JPA works is following

  • You fetch the object from the database.
  • You fetch the complete object, or having lazily loaded jpa provider's proxy object attached to the fetched object.
  • Do whatever modification you want.
  • You call the save method, it rans some internal dirty checking.
  • Based on the dirty checking, it generates the UPDATE sql statement.

In your case, you didn't fetch the isMigrated field during reading the entity and your entities default value set's it to 0.

What are the options

  1. If you don't want to update the isMigrated field from the jpa layer consider define the column annotation as @Column( name= "IS_MIGRATED", updatable = false)
  2. Fetch the isMigrated field, carry forward until you save the entity.
  3. (Worst idea ever) Instead of using save method write tedious JPQL like following inside the repository interface
@Modifying
@Query("UPDATE User user SET user.name = :name /*other set statements*/ WHERE user.id = :userId")
public void updateUser(@Param("name") String name, /*other properties*/, @Param("userId") int userId);

Response to the update in the question

The fields you are mentioning, deviceId, deviceIP these sort of fields are meant to be updated from the HTTPServlerRequest. I need to see your complete code. I guess before saving the users these properties are set from the HTTPServletRequest parameter. There are numerous way to setting these fields

  • Using @PrePersist, @PreUpdate inside the entity.
  • Setting these values from HTTPServletRequest inside service layer or controller layer.

This is why these values are setting to proper values, which is different from isMigrated field.

Ratul Sharker
  • 7,484
  • 4
  • 35
  • 44