1.1.14.4.2.7. fejezet, Hibernate tábla kapcsolatok

A Hibernate-tel a tábla kapcsolatok annotációval könnyen kezelhetők.

Request.java

package com.integrity.domain;
 
import java.io.Serializable;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.validation.constraints.NotNull;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
 
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;
 
import com.integrity.validator.FieldMatch;
 
@Entity
@Table(name="request")
@XmlRootElement
@FieldMatch.List({
    @FieldMatch(first = "discount", second = "discountReason", message = "{com.integrity.validation.discountReason.isEmpty.message}"),
    @FieldMatch(first = "operationType.id", second = "serviceId", message = "{com.integrity.validation.serviceid.isEmpty.message}")
})
public class Request implements Serializable {
    private static final long serialVersionUID = -3828086455549895099L;
 
    private Long id;
    private String name;
    private String phone;
    private String email;
    private String organization;
    private String subject;
    private OperationType operationType;
    @Transient
    private String operatoinTypeLabel;
    private String serviceId;
    private String homepage;
    private ApplicantType applicantType;
    @Transient
    private String applicantTypeLabel;
    private String startTime;
    @Transient
    private String startTimeLabel;
    private Boolean discount;
    private String discountReason;
    private String comment;
 
    public static String CONTRACTION_YES = "I";
    public static String CONTRACTION_NO = "N";
    public static String CONTRACTION_TRUE = "true";
    public static String CONTRACTION_FALSE = "false";
    public static String UNIFIED_YES_HU = "Igen";
    public static String UNIFIED_NO_HU = "Nem";
    public static String SERVICE_GROW = "B";
    public static String SERVICE_SWITCH = "L";
    public static String SERVICE_NEW = "U";
 
    /**
     * @return the id
     */
    @Id
    @GeneratedValue
    @Column(name="ID")
    @XmlAttribute
    public Long getId() {
        return id;
    }
    /**
     * @param id the id to set
     */
    public void setId(Long id) {
        this.id = id;
    }
    /**
     * @return the nev
     */
    @Column(name="name")
    @Length(max = 64)
    @XmlElement
    @NotNull
    @NotEmpty
    public String getName() {
        return name;
    }
    /**
     * @param name the nev to set
     */
    public void setName(String name) {
        this.name = name;
    }
    /**
     * @return the telefonszam
     */
    @Column(name="phone")
    @XmlElement
    @NotNull
    @NotEmpty
    public String getPhone() {
        return phone;
    }
    /**
     * @param phone the telefonszam to set
     */
    public void setPhone(String phone) {
        this.phone = phone;
    }
    /**
     * @return the email
     */
    @Email
    @Column(name="email")
    @XmlElement
    @NotNull
    @NotEmpty
    public String getEmail() {
        return email;
    }
    /**
     * @param email the email to set
     */
    public void setEmail(String email) {
        this.email = email;
    }
    /**
     * @return the szervezetnev
     */
    @Column(name="organization")
    @XmlElement
    @NotNull
    @NotEmpty
    public String getOrganization() {
        return organization;
    }
    /**
     * @param szervezetnev the szervezetnev to set
     */
    public void setOrganization(String organization) {
        this.organization = organization;
    }
    /**
     * @return the targy
     */
    @Column(name="subject")
    @XmlElement
    public String getSubject() {
        return subject;
    }
    /**
     * @param subject the targy to set
     */
    public void setSubject(String subject) {
        this.subject = subject;
    }
    /**
     * @return the muveletjelleg
     */
    //@Column(name="operation_type")
    @ManyToOne
    @JoinColumn(name = "operation_type", referencedColumnName = "id")
    @XmlElement
    @NotNull
    @NotEmpty
    public OperationType getOperationType() {
        return operationType;
    }
    /**
     * @param operationType the muveletjelleg to set
     */
    public void setOperationType(OperationType operationType) {
        this.operationType = operationType;
    }
    /**
     * @return the serviceID
     */
    @Column(name="service_id")
    @XmlElement
    public String getServiceId() {
        return serviceId;
    }
    /**
     * @param serviceId the serviceId to set
     */
    public void setServiceId(String service_id) {
        this.serviceId = service_id;
    }
    /**
     * @return the honlap
     */
    @Column(name="homepage")
    @XmlElement
    public String getHomepage() {
        return homepage;
    }
    /**
     * @param homepage the honlap to set
     */
    public void setHomepage(String homepage) {
        this.homepage = homepage;
    }
    /**
     * @return the kerelmezojelleg
     */
    @ManyToOne
    @JoinColumn(name = "applicant_type", referencedColumnName = "id")
    @XmlElement
    @NotNull
    @NotEmpty
    public ApplicantType getApplicantType() {
        return applicantType;
    }
    /**
     * @param applicantType the kerelmezojelleg to set
     */
    public void setApplicantType(ApplicantType applicantType) {
        this.applicantType = applicantType;
    }
    /**
     * @return the szolgaltatasido
     */
    @Column(name="start_time")
    @XmlElement
    @NotEmpty
    @NotNull
    public String getStartTime() {
        return startTime;
    }
    /**
     * @param startTime the szolgaltatasido to set
     */
    public void setStartTime(String startTime) {
        this.startTime = startTime;
    }
    /**
     * @return the kedvezmeny
     */
    @Column(name="discount")
    @XmlElement
    public Boolean getDiscount() {
        return discount;
    }
 
    public void setDiscount(Boolean discount) {
        this.discount = discount;
    }
 
    /**
     * @return the kedvezmenyoka
     */
    @Column(name="discount_reason")
    @XmlElement
    public String getDiscountReason() {
        return discountReason;
    }
    /**
     * @param discountReason the kedvezmenyoka to set
     */
    public void setDiscountReason(String discountReason) {
        this.discountReason = discountReason;
    }
    /**
     * @return the megjegyzes
     */
    @Column(name="comment")
    @XmlElement
    public String getComment() {
        return comment;
    }
    /**
     * @param comment the megjegyzes to set
     */
    public void setComment(String comment) {
        this.comment = comment;
    }
 
    @XmlElement
    @Transient
    public String getOperationTypeLabel() {
        return operatoinTypeLabel;
    }
 
    public void setOperationTypeLabel(String muveletjellegLabel) {
        this.operatoinTypeLabel = muveletjellegLabel;
    }
 
    @XmlElement
    @Transient
    public String getApplicantTypeLabel() {
        return applicantTypeLabel;
    }
 
    public void setApplicantTypeLabel(String applicantTypeLabel) {
        this.applicantTypeLabel = applicantTypeLabel;
    }
 
    @XmlElement
    @Transient
    public String getStartTimeLabel() {
        return startTimeLabel;
    }
 
    public void setStartTimeLabel(String startTimeLabel) {
        this.startTimeLabel = startTimeLabel;
    }
 
    @XmlElement
    @Transient
    public String getDiscountLabel() {
        return ((discount!=null) && discount ? UNIFIED_YES_HU : UNIFIED_NO_HU);
    }
 
}

A fenti kódban három mezőt érintett az annotációs kapcsolat: ApplicantType, StartTime, és OperationType. Az egyedi annotációs validáció (@FieldMatch) az utóbbi mezőnek megváltozott. Az operationType.id az objektum egyedi azonosítója. Ezt megtehetjük, mert a BeanUtils.getProperty() metódusa kezeli az objektumok mezőire való hivatkozást is.

    public boolean isValid(final Object value, final ConstraintValidatorContext context)
    {
        try
        {
            final Object firstObj = BeanUtils.getProperty(value, firstFieldName);
            final Object secondObj = BeanUtils.getProperty(value, secondFieldName);
 
            if (firstFieldName.equalsIgnoreCase("operationtype.id")){
              String operationTypeId = (String)firstObj;
              return operationTypeId.equals(Request.SERVICE_NEW) 
                      || (!secondObj.toString().isEmpty()
                         && (operationTypeId.equals(Request.SERVICE_SWITCH)
                            || operationTypeId.equals(Request.SERVICE_GROW)));  
            } else if (firstFieldName.equalsIgnoreCase("discount")) {
              return firstObj.toString().equalsIgnoreCase("false") || (!secondObj.toString().isEmpty() && firstObj.toString().equalsIgnoreCase("true"));
            }
        }
        catch (final Exception ignore)
        {
            // ignore
        }
        return true;
    }

A Trasient mezők úgy válnak feleslegessé, hogy Converter-t alkalmazunk a legördülő ablakoknál:

<p:selectOneMenu effect="fade" id="applicantType" label="#{text['request.applicant_type.label']}" value="#{serviceRequest.applicantType}">
  <f:selectItem itemLabel="Válasszon egyet" itemValue=""/>
  <f:selectItems value="#{myApplicantTypeDAO.getApplicantTypeList()}" var="applicantTypeVar" itemValue="#{applicantTypeVar.id}" itemLabel="#{applicantTypeVar.name}"/>
  <f:converter converterId="com.integrity.converter.ApplicantTypeConverter"/>
</p:selectOneMenu>

Az ApplicantTypeConverter így néz ki:

package com.integrity.converter;
 
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.servlet.http.HttpServletRequest;
 
import com.integrity.dao.ApplicantTypeDAO;
import com.integrity.domain.ApplicantType;
 
public class ApplicantTypeConverter implements Converter {
 
    public final static String CONVERTER_ID = "com.integrity.converter.ApplicantTypeConverter";
 
    private ApplicantTypeDAO applicantTypeDAO;
 
    public void setApplicantTypeDAO(ApplicantTypeDAO applicantTypeDAO) {
        this.applicantTypeDAO = applicantTypeDAO;
    }
 
    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null) {
            return null;
        }
        try {
            if (applicantTypeDAO == null) {
                HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
                applicantTypeDAO = (ApplicantTypeDAO)request.getSession().getAttribute("myApplicantTypeDAO");
            }
            if (applicantTypeDAO != null) {
                ApplicantType applicantType = applicantTypeDAO.getApplicantType(value);
                return applicantType;
            } else {
                ApplicantType applicantType = new ApplicantType();
                applicantType.setId(value);
                applicantType.setName(value);
                return applicantType;
            }
        } catch (NumberFormatException ex) {
            throw new ConverterException("Number converter exception (" + value + ").", ex);
        } catch (IllegalArgumentException ex) {
            throw new ConverterException("Illegal argument exception (" + value + ").", ex);
        } catch (Exception ex) {
            throw new ConverterException("Other conversion exception (" + value + ").", ex);
        }
    }
 
    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        if (value instanceof ApplicantType) {
            return ((ApplicantType) value).getId();
        } else {
            return value.toString();
        }
    }
 
}

Itt a példa egy session-ből előkeresett Bean-re. A myApplicantTypeDAO session láthatósági körrel (scope) rendelkezik. Ezt a Spring servlet-context.xml-ben definiáltuk.

<bean id="myApplicantTypeDAO" class="com.integrity.dao.ApplicantTypeDAOImpl" scope="session">
  <property name="sessionFactory" ref="mySessionFactory"/>
</bean>

Ezzel érhető el, hogy a Spring-ben nem definiált Converter lássa a DAO Bean-t. Még egy változtatást eszközölnünk kell az ApplicantType osztályban, hogy azonosítani tudjuk programunkban az ApplicantType osztályból gyártott objektumokat. Ez lényeges az objektum szöveges megjelenítésben és az egyedi azonosításban.

package com.integrity.domain;
 
import java.io.Serializable;
 
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
 
@Entity
@Table(name="applicant_type")
public class ApplicantType implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -1106868578703357156L;
 
    private String id;
 
    private String name;
 
    @Id
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public int hashCode(){
        int hash = 0;  
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }
 
    @Override
    public boolean equals(Object object){
        if (!(object instanceof ApplicantType)){
            return false;
        }
        return ((ApplicantType)object).id.equalsIgnoreCase(id);
    }
/*    
    @Override  
    public String toString() {  
        return "com.integrity.domain.ApplicantType[id=" + id + "]";
    }
//*/
}

A hashCode és az equals metódusokat kell felülírnunk. A toString akkor lehet szükséges, ha több mezős az egyedi azonosítás.

Példák a kapcsolat jellegére:

  • OneToOne : Az egyedi azonosítók (primary key) kötik össze a két tábla rekordjait, vagy 2 tábla +1 kapcsolótáblában a két tábla rekordjainak egyedi azonosítói
  • ManyToOne : Autók -> Szín (feltételezzük, hogy egy színű az autó)
  • OneToMany : Ember -> Lakhelyek (feltételezzük, hogy több helyen lakhat a személy)
  • Many2Many : Dátumok -> Események (feltételezzük, hogy egy dátumra több esemény történhet, és egy esemény több napon is előfordul)