我的数据库包含下表:
表:
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"
}
我还使用调试器检查了相同的行为,发现内存中的对象实际上具有相同的行为。
我想要的:我希望返回的数据与数据库中的数据相同。
答案 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来解决这个问题。
如上所述,不建议使用业务/自然密钥作为主键,因为随着业务规则的变化,业务规则可能会在未经许可的情况下发生变化,因此可能会在未来发生变化!+如果您正在使用{{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;
//...
}
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();
}
}
在向存储库添加自定义函数时,应始终删除默认的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。