第1 页 共1页1

使用JPA的时候,如果A B两个实体间是一对多,多对一的关系,如果不在@OneToMany里加入mappedBy属性会导致自动生成一个多余的中间表。比如:

@Entity
public class A {
    @OneToMany(mappedBy="a")
    public Set<B> bs = new HashSet<B>(0);
}
 
@Entity
public class B {
    @ManyToOne
    public A a;
}

这样写会只成生成表A和表B,B中会有一个到表A的外键。但是如果不加mappedBy=”a”, 那么就会再生成一张A_B表。

Share/Save/Bookmark

有两个实体Order和Customer,一个用户可以有多个订单,一个订单属于一个用户,所以Customer与Order是一对多的关系,那么在Order实体里可以有一个到Customer的引用customer,同时在Customer里有一个Order实体的集合orders。如果在只是如下这样写:

public class Order {
    @id
    private long id;
 
    @ManyToOne
    private Customer customer;
 
    public void setId(long id) {
        this.id = id;
    }
 
    public long getId() {
        return this.id;
    }
 
    public void setCustomer(Customer customer) {
        this.customer = customer;
    }
 
    public Customer getCustomer() {
        return this.customer;
    }
}
 
public class Customer{
    @id
    private long id;
 
    @OneToMany
    private Set<Order> orders;
 
    public void setId(long id) {
        this.id = id;
    }
 
    public long getId() {
        return this.id;
    }
 
    public void setOrders(Set<Order> orders) {
        this.orders = orders;
    }
 
    public Set<Order> getOrders() {
        return this.orders;
    }
}

那么系统可能会自动生成一张Customer_Order关联表,可是事实上我们不需要这样的表,因为通过 Select order from Order order where order.customer.id=:id, 就可以找到某个Customer的Orders,而不需要另建一张表。要想去掉这个自动生成的表,要这样写:

public class Order {
    @id
    private long id;
 
    @ManyToOne
    @JoinColumn(name="customer", nullable=false)
    private Customer customer;
 
    public void setId(long id) {
        this.id = id;
    }
 
    public long getId() {
        return this.id;
    }
 
    public void setCustomer(Customer customer) {
        this.customer = customer;
    }
 
    public Customer getCustomer() {
        return this.customer;
    }
}
 
public class Customer{
    @id
    private long id;
 
    @OneToMany(mappedBy="customer")
    private Set<Order> orders;
 
    public void setId(long id) {
        this.id = id;
    }
 
    public long getId() {
        return this.id;
    }
 
    public void setOrders(Set<Order> orders) {
        this.orders = orders;
    }
 
    public Set<Order> getOrders() {
        return this.orders;
    }
}

多出一个mappedBy和JoinColumn, 就是告诉JPA实现者,orders是通过order里的customer映射来的,每次查找orders通过Order里的customer联接。这样就不会生成Customer_Order关联表了

Share/Save/Bookmark

j2ee难用,其中一个原因是jndi不好用,明明已经配置好名字为dataSource的数据源,但是在部署JPA时,老是抛异常说找不到数据源。今天在用jboss时,终于又明白了一些。

原来j2ee中的组件是有它自己独自的环境的,位于java:/comp/env这个jndi目录下。每个组件不能访问另外一个组件的私有环境,实现组件各自独立。同时,又有一些jndi目录和名字是公共的,如java:/下的其它一些名字,还有一些是只能给本地访问的,还有一些是能够给远程访问的,这个就不说了。

如果一个组件想要获得另外一个组件,那么需要在它的部署描述符里说明,比如:

<resource-ref>
  <description>Dollys DataSource</description>
  <res-ref-name>jdbc/mydatasource</res-ref-name>
  <res-ref-type>javax.sql.DataSource</res-ref-type>
  <res-auth>Container</res-auth>
</resource-ref>

这里说这个组件需要依赖于一个叫java:/comp/env/jdbc/mydatasource的数据源。但是这个数据源又从哪里来呢??!!注意了!原来是要在部署时写另外一个特定于服务器种类的数据源映射文件,如下面的jboss示例:

<resource-ref>
   <res-ref-name>jdbc/mydatasource</res-ref-name>
   <jndi-name>java:DefaultDS</jndi-name>
</resource-ref>

上面这一段可能位于jboss-web.xml等jboss特定的部署描述符里。目的就是告诉jboss,把全局的java:DefaultDS映射到这个组件的java:/comp/env/jdbc/mydatasource。就是这一步,很时候之所以部署失败,找不到jndi都是因为少了这样一个映射导致的。

如果不想写上面的特定于服务器的部署描述符,我估计可以通过指定一个公共的jndi来解决,如下:

<resource-ref>
  <description>Dollys DataSource</description>
  <res-ref-name>java:DefaultDS</res-ref-name>
  <res-ref-type>javax.sql.DataSource</res-ref-type>
  <res-auth>Container</res-auth>
</resource-ref>

问题虽然解决了,可是突然觉得好恶心,搞得这么复杂有必要吗?!!j2ee果然不是为了做小项目来的。

Share/Save/Bookmark

September 24th, 2007Java Persistence笔记(1)

实体类(Entity Class):需要被持久化的类。为了使一个类能够被持久化,这个类需要满足一系列的约束。

持久化提供者(Persistence Provider):能够将实体类持久化的持久化功能提供者,比如hibernate, JDO, Toplink等。

对实体类的配置,不依赖于具体的持久化提供者,也就是说,在实体类配置好后,更换持久化提供者,不需要修改实体类。也就是说JPA提供了一个标准,实现了实体类标准的类,可以被任何一个实现在持久化提供者标准的框架持久化。所以在用JPA做持久化时,程序员可以集中注意力在实体类的实现上,而不必太关心具体的持久化提供者。

一个能够被持久化的实体类,必须有两部分,一是需要被持久的类,二是对于这个类的持久化配置信息。实 体类必须满足以下约束:

  1. 被javax.persistence.Entity 标注,或者在XML配置文件里被显式声明为实体类。
  2. 拥有一个public 或 protected的空参数的构造方法。
  3. enum和interface不能做实体类。
  4. 不可以是final类,持久化类和实例变量不能是final的。
  5. 如果实体类的实例需要在不被持久化提供者管束的情况下被以传值的方式传递,那么实体类需要实现java.io.Serializable接口
  6. 实体类之间支持继承,多态联接和多态查询。
  7. 抽像类和具像类都可以做为实体类。实体类可以扩展实体类,也可以扩展非实体类,同样的,非实体类也可以扩展实体类。
  8. 一个实体的状态是通过实体的实例变量来表示的,而实例变量可能是和Java beans的属性相对应。实体类的实例属性只能被实体在实体自己的方法内直接访问。实体的实例变量不能被实体的客户直接访问。实体的客户只能通过访问器(getter and setter)或者其它的业务方法来访问实体的变量。实体的实例变量必须是private, protected 或者 package 可见的(不能是public的)。

典型的持久化类如下所示:

@Entity
@Table(name="PRODUCT")
public class Product {
    @Id
    private Long id;
    private String name;
    private BigDecimal price;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public BigDecimal getPrice() {
        return price;
    }
 
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}

持久化提供者有两种方式访问实体类,一是直接访问实体的实例变量(field access),二是通过实例变量的访问器(JavaBean 模式 property access)。当用annoation来标注实体类时,如果标注在实例变量上,那么将通过实例变量直接访问,如果标注在访问器(getter)上,则通过访问器以JavaBean属性的方式访问。如果既有标注在实例变量上的,又有标注在访问器上的,那么持久化提供者的访问方式将不可知,这是我们应该避免的情况。如果在XML里对已经标注过的实体类进行配置,指定了一种不同于标注所指定的访问方式,那么持久化提供者的访问方式也是不可知的。

下面所示的实体类的访问方式将不可知,因为属性product的getter上,和其它实例变量上都有标注:

 

@Entity
@Table(name="ORDER")
public class Order {
    @Id
    private Long id;
    private int quantity;
    private Date transdate;
    @ManyToOne
    private Buyer buyer;
    private Product product;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public Buyer getBuyer() {
        return buyer;
    }
 
    public void setBuyer(Buyer buyer) {
        this.buyer = buyer;
    }
 
    @ManyToOne public Product getProduct() {
         return product;
    }
 
    public void setProduct(Product product) {
        this.product = product;
    }
 
    public int getQuantity() {
        return quantity;
    }
 
    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
 
    public Date getTransdate() {
        return transdate;
    }
 
    public void setTransdate(Date transdate) {
        this.transdate = transdate;
    }
}

如果持久化提供者以实例变量的方式访问实体,那么实体的所有未被标为java.persistence.Transient的非 transient 实例变量都会被持欠化,类似的,如果以访问器方式来访问,那么未被标注为java.persistence.Transient的Javabean 属性都会被持久化。

映射用的标注(javax.persistence.ManyToOne等)不能加在transient 或者javax.persistence.Transient的实例变量和属性上。

持久化实例变量或者属性,当为集合类型时,必须声明为如下接口或者其泛化:java.util.Collection, java.util.Set, java.util.List, java.util.Map。

当采用访问器方式访问时,应注意:

  1. 访问器里可以包含有业务逻辑,比如对数据的验证,格式化等。但是,持久化提供者调用访问器的顺利是不定的,所以不能在访问器中放置依赖于访问顺序的逻辑代码。
  2. 如果属性被标为延迟载入(lazy load),那么就不能在属性的访问器被持久化提供者真正调用过之前直接访问属性对应的实例变量。也就是说任何时候读写属性都尽量用访问器。

下面的实体类用访问器方式,而访问器setName被设置成依赖于访问顺序的,这将导致问题:

@Entity
@Table(name="MAN")
public class Man {
    private Long id;
    private String name;
    private String sex;
 
    @Id
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        if("male".equals(sex)) {
            this.name = "Mr." + name;
        } else {
            this.name = "Ms." + name;
       }
    }
 
     public String getSex() {
        return sex;
     }
 
    public void setSex(String sex) {
        this.sex = sex;
    }
}

设计者希望setSex在setName之前被调用,但是访问器的调用顺序是不定的,可以会出现先setName后setSex的情况。

实体类的子类可以覆盖父类的访问器,但是不要覆盖父类里实例变量或者属性的持久化元数据。

Share/Save/Bookmark


第1 页 共1页1
© 2007 涂0实验室 | iKon Wordpress Theme by Windows Vista Administration | Powered by Wordpress