JPA entity not being created with default value with migration query

Viewed 705

I am adding a new column, signature, to my existing user table -- using FlyawayDB for DB migration -- where the new column will have a default value, for both new and existing users. However, I noticed that when I try creating and saving a new user, that field is null for some reason.

I don't know if is this is some caching issue (e.g. it's using some version of the user object before my V2__adding_signature.sql migration query is executed) or if something else.

Here is what I have:

Entity

@Entity
@Table(name = "users")
@Data
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@DynamicUpdate
public class UserEntity {
  @Id private String userId;

  @Column(insertable = false, updatable = false)
  private String signature;

  public UserEntity(String userId) {
    this.userId = userId;
  }
}

Service

@Service
@AllArgsConstructor
public class UserService {

  private final UserRepository userRepo;

  @Transactional
  public UserEntity createUser(String userId) {
    UserEntity userEntity = new UserEntity(userId);
    userRepo.saveAndFlush(userMappingEntity);
    return userEntity; // this will not have a the default signature created
  }
}

Repository

@Repository
public interface UserRepository extends JpaRepository<UserEntity, String> {

....
}

Initial PostGres DB set up in my db.migration folder (for Flywaydb)

V1__initial_creation.sql

CREATE TABLE IF NOT EXISTS users (
  user_id TEXT PRIMARY KEY
);

V2__adding_signature.sql

ALTER TABLE users
    ADD COLUMN signature TEXT NOT NULL DEFAULT 'testValueForNow';

EDIT_1:

I have also tried to use the EntityManager to fresh my entity, like so

  @PersistenceContext private EntityManager entityManager;

      @Transactional
      public UserEntity createUser(String userId) {
        UserEntity userEntity = new UserEntity(userId);
        userRepo.saveAndFlush(userEntity);
        entityManager.refresh(userEntity);

        return userEntity; // this will not have a the default signature created
      }

however this doesn't seem to work either, and gives me the following error: java.lang.IllegalArgumentException: Entity not managed

EDIT_2: Thanks to user @Simon Martinelli

I had to do the following to get it to work:

   UserEntity userEntity = new UserEntity(userId);
   userEntity = userRepo.saveAndFlush(userEntity);
   entityManager.refresh(entityManager.merge(userEntity));
   return userEntity;
2 Answers

The default value only applies in the database.

After insert you have to refresh the entity to get the inserted default value from the database.

You should use the @Generated annotation for the signature field in this way:

@Generated(value = GenerationTime.ALWAYS)
@Column(insertable = false, updatable = false)
private String signature;

So, hibernate will be able to fetch the property after the entity has been persisted or updated.

Another one piece of your problem is the following code:

@Transactional
public UserEntity createUser(String userId) {
   UserEntity userEntity = new UserEntity(userId);
   userRepo.saveAndFlush(userMappingEntity);
   return userEntity; // this will not have a the default signature created
}

It is correct when you know for sure that a user with userId does not exist. But when the user with userId is actually present in the database the column signature does not fetch. So, you should correct this method in this way:

@Transactional
public UserEntity createUser(String userId)
{
   Optional<User> user = userRepo.findById(userId);
   if (user.isPresent()) return user.get();

   UserEntity userEntity = new UserEntity(userId);
   return userRepo.save(userEntity);
}

I guess that saveAndFlush can be replaced by save because the flushing will be done anyway in the end of createUser method as it marked as @Transactional.

Related