从数据库中检索的实体与查询中的情况相同

时间:2017-10-25 06:08:23

标签: java spring hibernate spring-data-jpa

我的数据库包含下表:

表:

country {
    code varchar(255) not null
        primary key
};

类:

@Entity
public class Country {
    @Id
    @Column(name = "code")
    private String mCode;

    public String getCode() {
        return mCode;
    }

    public void setCode(String code) {
        mCode = code;
    }
}

示例表行:

| code |
|------|
| USA  |
| UK   |

当我使用以下CrudRepository检索国家时:

public interface CountryRepository extends CrudRepository<Country, String> {
}

第一种情况:

mRepository.findOne("USA")

它会在我的休息api中给出以下结果:

{
  "code": "USA"
}

第二种情况:

mRepository.findOne("UsA")

它会在我的休息api中给出以下结果:

{
  "code": "UsA"
}

第三种情况:

mRepository.findOne("Usa")

它会在我的休息api中给出以下结果:

{
  "code": "Usa"
}

我还使用调试器检查了相同的行为,发现内存中的对象实际上具有相同的行为。

我想要的:我希望返回的数据与数据库中的数据相同。

4 个答案:

答案 0 :(得分:7)

正如@Bedla在评论中已经暗示的那样,您可能在数据库中使用不区分大小写的varchar数据类型。但是,这不建议用于Hibernate中的主键(通常),因为Hibernate在引用持久化上下文中的实体(如果启用了二级缓存)时依赖于id属性值唯一性。

例如,在按"USA"加载实体后,再按"usa"加载实体(或合并分离的"usa",同时加载"USA"后等)相同的持久化上下文可能会在持久化上下文中结束两个不同的实例,这意味着它们将被单独刷新,从而覆盖彼此的更改等。

而是在数据库中使用区分大小写的数据类型,但允许通过忽略大小写进行搜索:

public interface CountryRepository extends CrudRepository<Country, String> {
    @Query("select c from Country c where upper(c.mCode) = upper(?1)")
    Country getCountry(String code);
}

PS国家/地区代码通常不适合主键,因为它们可以更改。

答案 1 :(得分:0)

尝试覆盖实体中的equals()hashcode()方法,以便考虑mCase的情况(并且不要使用这两种方法中的任何其他字段)。

答案 2 :(得分:0)

你可以用另一层(如服务或控制器)包装CrudRepository。

public Country findOne(String code){
    Country country = mRepository.findOne(keyWord)
    if (country !=null){
        country.setCode(code)
        return country;
    }
    return null;
}

从mRepository返回的实体是存储在数据库中的实体。我认为你应该有另一种方法来进行这种特殊处理。

答案 3 :(得分:-2)

通过调用repository.findOne("Usa")(默认实现为SimpleJpaRepository.findOne),Hibernate将使用实例化实体的EntityManager.find(如果它在数据库中找到并且不存在于第一级和第二级缓存中)并且使用SessionImpl.instantiate方法将传递的参数值设置为主键,而不是使用Select查询结果。 我已经在Hibernate Jira中填写ticket来解决这个问题。

解决方案1:

不要将自然ID用作主键:

如上所述,不建议使用业务/自然密钥作为主键,因为随着业务规则的变化,业务规则可能会在未经许可的情况下发生变化,因此可能会在未来发生变化!+如果您正在使用{{3对于主键,字符串可能会慢一些,也许你会在数据库中找到两行:Country('USA')Country('USA '),而不是使用clustered index,你可以检查这两个SO有关详细信息的问题:

如果您选择此选项,请不要忘记使用Strings as Primary Keys in SQL Database映射商家密钥的唯一约束:

@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = "code"))
public class Country {

    // Your code ...     

    @Column(name = "code", nullable=false)
    private String mCode;

    //...     
}

解决方案2:

更改Country.mCode案例:

如果您有可能将所有Country.code存储在UpperCase中:

@Entity
public class Country {


    private String mCode;

    protected Country(){
    }

    public Country(String code){
       this.mCode = code.toUpperCase()
    }

    @Id
    @Column(name = "code")
    public String getCode() {
        return this.mCode;
    }

   // Hibernate will always use this setter to assign mCode value
   private void setCode(String code) {
       this.mCode = code.toUpperCase();
   }

}

解决方案3:

自定义CrudRepository:

在向存储库添加自定义函数时,应始终删除默认的findOne(String),以便强制其他人使用“最安全”的方法。

解决方案3.1:

使用@UniqueConstraint mRepository查找方法(我将其命名为findOneWithRightStringCase):

public interface CountryRepositoryCustom {
    Country findOneWithRightStringCase(String id);    
}

public class CountryRepositoryImpl implements CountryRepositoryCustom{

    @PersistenceContext private EntityManager entityManager;

    @Override
    public Country findOneRespectCase(String id) {
        try {
            return entityManager.createQuery("SELECT c FROM Country c WHERE c.mCode=:code", Country.class).setParameter("code", id).getSingleResult();

        } catch (NoResultException ex) {
            return null;
        }

    }
}

public interface CountryRepository extends CrudRepository<Country, String>, CountryRepositoryCustom {   

    @Override
    public default Country findOne(String id) {
        throw new UnsupportedOperationException("[findOne(String)] may not match the case stored in database for [Country.code] value, use findOneRespectCase(String) instead!");
    }
}

解决方案3.2:

您可以为您的存储库添加custom implementations

public interface CountryRepository extends CrudRepository<Country, String> {
    Country findByMCode(String mCode);

    @Override
    public default Country findOne(String id) {
        throw new UnsupportedOperationException("[findOne(String)] may not match the case stored in database for [Country.code] value, use findByMCode(String) instead!");
    }
}

或使用Query methods@Query):

public interface CountryRepository extends CrudRepository<Country, String> {
    @Query("select c from Country c where c.mCode= ?1")
    Country selectCountryByCode(String mCode);

    @Override
    public default Country findOne(String id) {
        throw new UnsupportedOperationException("[findOne(String)] may not match the case stored in database for [Country.code] value, use selectCountryByCode(String) instead!");
    }
}

希望它有所帮助!

注意:我正在使用Spring Data 1.11.8.RELEASE和Hibernate 5.2.10.Final。