http://shinetech.asia/thoughts/articles/63-real-world-experiences-with-hibernate?start=3

Real-World Experiences With Hibernate - Best Practices


'Best Practices'

Real-World Experiences With Hibernate says that we won't be advocating such practices?". Well, the Hibernate team has it's own "Best Practices" - and we've found that you can get into a mess if you apply them prescriptively without thinking about it. This section aims to examine some of those practices.

Components and Queries

Components provide an excellent technique for refactoring. However, one nuisance that we've encountered is that you cannot drop a component into a query. Consider the following component definition:

@javax.persistence.Embeddable
public class MembershipId {
    private int memberPrefix;

    private int memberNumber;

    int getMemberPrefix() {
        return memberPrefix;
    }

    int getMemberNumber() {
        return memberNumber;
    }
}

Say that this component is used in another entity:

@javax.persistence.Entity
public class MemberNote {
    @javax.persistence.Id
    private int id;

    private MembershipId membershipId;

    private String text;
}

and that you wanted to write a DAO method that returned all of the member notes for a particular membership ID. You can't just drop the component into a HQL query:

public List getMemberNotes(MembershipId membershipId) {
    return session.createQuery(
            "from MemberNote where membershipId=:membershipId")
            .setParameter("membershipId", membershipId).list();
}

Because it'll generate SQL that looks like this:

select membernote0_.id as id2_, membernote0_.isocode as isocode2_, 
       membernote0_.memberNumber as memberNu3_2_, 
       membernote0_.text as text2_ 
from MemberNote membernote0_ 
where (membernote0_.isocode, membernote0_.memberNumber)=?

This doesn't work with Oracle or HSQLDB. Instead, you have to fully qualify each part of the key:

public List getMemberNotes(MembershipId membershipId) {
    return session.createQuery(
            "from MemberNote where "
                    + "membershipId.memberNumber=:memberNumber and "
                    + "membershipId.memberPrefix=:memberPrefix")
            .setInteger("memberNumber", membershipId.getMemberNumber())
            .setInteger("memberPrefix", membershipId.getMemberPrefix())
            .list();
}

The documentation says "(n)ever try to use a path-expression that ends in a property of component type"(see here) . Frankly we're a little unsure as to why Hibernate behaves this way with simple equality checks. It'd be understandable if we tried to do a more complex comparison like a greater-than or less-than.

Incidentally, if you repeatedly find yourself doing foreign-key lookups like this, it might be worth mapping a relationship instead and have Hibernate do the work.

Generated Keys

The Hibernate documentation recommends the use of synthetic primary keys (see here) and in our experience, Hibernate works best with them.

However, it can be difficult to test business logic code that is dependent on the value of generated keys. This is because you have reduced control over what keys are generated. For example, say that you had a synthetic primary key on a class and decided that you would sort results by this ID. Because this ID is generated, it can be difficult to ensure that objects will have IDs generated in a particular order and to then check that these objects are sorted correctly.

To summarise, if you're using generated synthetic primary keys, it's best to just set them up so that Hibernate can use them, and then forget about them. Consider them a requirement of Hibernate's internal operation. If possible, don't use them yourself - not if you want to test them anyway.

The equals() Method

A prime example of a Hibernate "best practice" that has caused us problems is the equals() method. However, let me emphasis that the question is not if you should implement the equals() and hashCode() methods - the testing benefits alone make it worthwhile - but how you should implement them.

The Hibernate documentation recommends using "Business key equality" (see here to see what this means). Whilst this technique is certainly the most appropriate from a Hibernate point-of-view, we ran into problems when other users of the database (who were not even using Hibernate) inserted rows with duplicated 'business keys'. This caused our code to behave in unexpected ways.

Given that the columns we had dictated to be the "business key" did not actually constitute the real primary key, it rapidly became apparent that it was unreasonable to expect other users to not insert whatever they want into them. Consequently, we had to revert back to use of the primary key in the equals() method. Unfortunately, a large number of tests that had been written to set up "business key" data had to be modified.

Whilst usage of primary keys in equals() has its downsides (for example, unsaved objects that use a key generator cannot be put into sets), the sad fact in this case was that from the perspective of all users of the system, a primary key was a primary key - and a 'business key' wasn't!

For those cases where you have a composite primary key, we highly recommend implementing your equals() and hashCode() methods with the Jakarta Commons libraries - in particular the EqualsBuilder and HashCodeBuilder classes.

Custom Types

The Hibernate team recommends the use of custom types where possible. These can be extremely useful, but have one significant limitation: instances of custom types that map to multiple columns can't be put into HQL queries - at least not if your database doesn't support tuples (which Oracle doesn't).

For example, we had a database where dates were stored in separate day, month and year columns. This occurred in many tables, so we wrote a custom type and used it when mapping to these tables. Here is an example of its usage:

import javax.persistence.Column;

@javax.persistence.Entity
public class Membership {
    @javax.persistence.Id
    private int id;

    @org.hibernate.annotations.Type(type = "DateUserType")
    @org.hibernate.annotations.Columns(columns = {
            @Column(name = "BIRTH_DD"), @Column(name = "BIRTH_MM"),
            @Column(name = "BIRTH_YYYY") })
    private java.util.Date birthDate;
}

However, attempts to pass this date directly into a query failed:

public List getMemberships(Date birthDate) {
    return session.createQuery(
            "from Membership m where m.birthDate=:birthDate").setParameter(
            "birthDate", birthDate,
            org.hibernate.Hibernate.custom(DateUserType.class)).list();
}

Because Hibernate will generate the following SQL:

select membership0_.id as id3_, membership0_.MBR_BIRTH_DD as MBR2_3_,
       membership0_.MBR_BIRTH_MM as MBR3_3_, 
       membership0_.MBR_BIRTH_YYYY as MBR4_3_ 
from Membership membership0_
where (membership0_.MBR_BIRTH_DD, membership0_.MBR_BIRTH_MM, membership0_.MBR_BIRTH_YYYY)=?

(Note that this is similar to the problem described in Components and Queries).

In our case, the only thing that we were able to do was to define a component that wraps the date:

import java.util.Calendar;
import java.util.Date;

@javax.persistence.Embeddable
public class DateWrapper
{
    private int dayOfMonth;
    private int month;
    private int year;
    
    @javax.persistence.Transient
    private final Calendar calendar = Calendar.getInstance();
    
    DateWrapper(Date date) {        
        calendar.setTime(date);
        dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
        month = calendar.get(Calendar.MONTH);
        year = calendar.get(Calendar.YEAR);
    }
    
    Date getDate()
    {
        calendar.clear();        
        calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
        calendar.set(Calendar.MONTH, month);
        calendar.set(Calendar.YEAR, year);            
        return calendar.getTime();
    }

    int getDayOfMonth() {
        return dayOfMonth;
    }

    int getMonth() {
        return month;
    }

    int getYear() {
        return year;
    }
}

and use it to map properties that were going to be used in HQL queries:

import javax.persistence.AttributeOverride;
import javax.persistence.Column;

@javax.persistence.Entity
public class Membership {
    @javax.persistence.Id
    private int id;

    @javax.persistence.AttributeOverrides({
            @AttributeOverride(name = "dayOfMonth", column = @Column(name = "BIRTH_DD")),
            @AttributeOverride(name = "month", column = @Column(name = "BIRTH_MM")),
            @AttributeOverride(name = "year", column = @Column(name = "BIRTH_YYYY")) })
    private DateWrapper birthDate;
}

Because the type of dateOfBirth was changed from Date to DateWrapper, any code that was using dateOfBirth directly needed to be modified to use DateWrapper.getDate(). We also needed to modify our DAO to explicitly qualify each part of the component:

public List getMemberships(Date birthDate) {
    DateWrapper birthDateWrapper = new DateWrapper(birthDate);
    String dayOfMonthParameterName = "dayOfMonth";
    String monthParameterName = "month";
    String yearParameterName = "year";
    return session.createQuery(
            "from Membership where birthDate.dayOfMonth=:"
                    + dayOfMonthParameterName + " and birthDate.month=:"
                    + monthParameterName + " and birthDate.year=:"
                    + yearParameterName).setInteger(
            dayOfMonthParameterName, birthDateWrapper.getDayOfMonth())
            .setInteger(monthParameterName, birthDateWrapper.getMonth())
            .setInteger(yearParameterName, birthDateWrapper.getYear())
            .list();
}

Note that as far as we can tell, this problem only occurs when you have a custom type that maps to multiple columns. In short, custom types are still very useful, but may not be worth using for when you are mapping to multiple columns and need to use the mapped property in a HQL query.


1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REaDME.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值