`
wj98127
  • 浏览: 263761 次
  • 性别: Icon_minigender_2
  • 来自: 北京
社区版块
存档分类
最新评论

EJB3.0-JPA实体的注解规范以及Hibernate特有的扩展(下)

    博客分类:
  • Java
阅读更多
   有时候,你想让数据库,而非JVM,来替你完成一些计算,也可能想创建某种虚拟列.
  你可以使用SQL片段(亦称为公式),而不是将属性映射到(物理)列. 这种属性是只读的(属性值由公求得).
@Formula("obj_length * obj_height * obj_width")
  public long getObjectVolume()

  SQL片段可以是任意复杂的,甚至可包含子查询.

@org.hibernate.annotations.Type
覆盖了Hibernate所用的默认类型:这通常不是必须的,因为类型可以由Hibernate正确推得.
关于Hibernate类型的详细信息,请参考Hibernate使用手册.
@org.hibernate.annotations.TypeDef 和@org.hibernate.annotations.TypeDefs允许你来声明类型定义.
  这些注解被置于类或包一级.注意,对session factory来说,这些定义将是全局的(即使定义于类一级),并且类型定义必须先于任何使用.
@TypeDefs(
    {
    @TypeDef(
        name="caster",
        typeClass = CasterStringType.class,
        parameters = {
            @Parameter(name="cast", value="lower")
        }
    )
    }
)
package org.hibernate.test.annotations.entity;

...
public class Forest {
    @Type(type="caster")
    public String getSmallText() {
    ...
}
        
当使用组合的用户自定义类型时,你必须自己表示列的定义.
@Columns就是为了此目的而引入的.
@Type(type="org.hibernate.test.annotations.entity.MonetaryAmountUserType")
@Columns(columns = {
    @Column(name="r_amount"),
    @Column(name="r_currency")
})
public MonetaryAmount getAmount() {
    return amount;
}

public class MonetaryAmount implements Serializable {
    private BigDecimal amount;
    private Currency currency;
    ...
}
     
通过在列属性(property)上使用@Index注解,可以在特定列上定义索引,columnNames属性(attribute)将随之被忽略.

@Column(secondaryTable="Cat1")
@Index(name="story1index")
public String getStoryPart1() {
    return storyPart1;
}

在嵌入式对象内部,你可以在那些指向该嵌入式对象所属元素的属性上定义该注解.
@Entity
public class Person {
    @Embeddable public Address address;
    ...
}

@Embeddable
public class Address {
    @Parent public Person owner;
    ...
}

person == person.address.owner
       
  某些属性可以在对数据库做插入或更新操作的时候生成.
  Hibernate能够处理这样的属性,并触发一个后续的查询来读取这些属性.
 
@Entity
public class Antenna {
    @Id public Integer id;
    @Generated(GenerationTime.ALWAYS) @Column(insertable = false, updatable = false)
    public String longitude;

    @Generated(GenerationTime.INSERT) @Column(insertable = false)
    public String latitude;
}
    
  你可以将属性注解为@Generated.
  但是你要注意insertability和updatability不要和你选择的生成策略冲突.
  如果选择了GenerationTime.INSERT,该属性不能包含insertable列,
  如果选择了GenerationTime.ALWAYS,该属性不能包含insertable和updatable列.

        @Version属性不可以为
        @Generated(INSERT)(设计时), 只能是
        NEVER或ALWAYS.

      SINGLE_TABLE 是个功能强大的策略,但有时,特别对遗留系统而言,
  是无法加入一个额外的辨别符列.
  由此,Hibernate引入了辨别符公式(discriminator formula)的概念:
  @DiscriminatorFormula是@DiscriminatorColumn的替代品,
  它使用SQL片段作为辨别符解决方案的公式( 不需要有一个专门的字段).

      @Entity
@DiscriminatorForumla("case when forest_type is null then 0 else forest_type end")
public class Forest { ... }

      默认情况下查询顶级实体,Hibernate不会加入带鉴别器列的约束条件子句.
      但是如果该列中还包含了和继承层次无关的值(通过@DiscriminatorValue)
      就会很不方便.为了解决这个问题,你可以在类上使用@ForceDiscriminator注解
      (将该注解放在@DiscriminatorColumn后面).
      这样Hibernate在加载实体的时候就可以列出对应的值.

      默认情况下,当预期的被关联元素不在数据库中(关乎关联列的错误id),致使Hiberante无法解决关联性问题时,Hibernate就会抛出异常.
      这对遗留schema和历经拙劣维护的schema而言,这或有许多不便.
      此时,你可用 @NotFound 注解让Hibernate略过这样的元素而不是抛出异常.
      该注解可用于 @OneToOne (有外键)、 @ManyToOne 、 
      @OneToMany 或 @ManyToMany 关联.

      @Entity
public class Child {
    ...
    @ManyToOne
    @NotFound(action=NotFoundAction.IGNORE)
    public Parent getParent() { ... }
    ...
}

   有时候删除某实体的时候需要触发数据库的级联删除.

      @Entity
public class Child {
    ...
    @ManyToOne
    @OnDelete(action=OnDeleteAction.CASCADE)
    public Parent getParent() { ... }
    ...
}

      上面这个例子中,Hibernate将生成一个数据库级的级联删除约束.

      Hibernate生成的外键约束的名字可读性相当差,
      你可以使用@ForeignKey注解覆盖自动生成的值.

      @Entity
public class Child {
    ...
    @ManyToOne
    @ForeignKey(name="FK_PARENT")
    public Parent getParent() { ... }
    ...
}

alter table Child add constraint FK_PARENT foreign key (parent_id) references Parent
 
  EJB3为延迟加载和获取模式提供了fetch选项,而Hibernate
  这方面提供了更丰富的选项集.为了更好的调整延迟加载和获取策略,Hibernate引入了
  一些附加的注解:
@LazyToOne: 定义了@ManyToOne 和 @OneToOne
关联的延迟选项. LazyToOneOption 可以是PROXY (例如:基于代理的延迟加载),NO_PROXY (例如:基于字节码增强的延迟加载 - 注意需要在构建期处理字节码)和 FALSE (非延迟加载的关联)

@LazyCollection: 定义了@ManyToMany和@OneToMany  关联的延迟选项. LazyCollectionOption可以是TRUE (集合具有延迟性,只有在访问的时候才加载),   EXTRA (集合具有延迟性,并且所有的操作都会尽量避免加载集合,对于一个巨大的集合特别有用,因为这样的集合中的元素没有必要全部加载)和 FALSE(非延迟加载的关联)
    
   @Fetch:
   定义了加载关联关系的获取策略. FetchMode 可以是SELECT (在需要加载关联的时候触发select操作), SUBSELECT(只对集合有效,使用了子查询策略,详情参考Hibernate参考文档) or JOIN (在加载主实体(owner entity)的时候使用SQL JOIN来加载关联关系).
JOIN 将覆写任何延迟属性(通过JOIN策略加载的关联将不再具有延迟性).
The Hibernate annotations overrides the EJB3 fetching options.

Hibernate注解覆写了EJB3提供的获取(fetch)选项.
             
Annotations
Lazy
Fetch

@[One|Many]ToOne](fetch=FetchType.LAZY)
@LazyToOne(PROXY)
@Fetch(SELECT)
@[One|Many]ToOne](fetch=FetchType.EAGER)
@LazyToOne(FALSE)
@Fetch(JOIN)
@ManyTo[One|Many](fetch=FetchType.LAZY)
@LazyCollection(TRUE)
@Fetch(SELECT)
@ManyTo[One|Many](fetch=FetchType.EAGER)
@LazyCollection(FALSE)
@Fetch(JOIN)

以下是可能的设置方式
用@BatchSizebatch设置集合的batch大小
用@Where或@WhereJoinTable注解设置Where子句,
这两种注解分别应用于目标实体和关联表
           
用注解@Check来设置check子句
用注解@OrderBy来设置SQL的order by子句
利用@OnDelete(action=OnDeleteAction.CASCADE) 注解设置级连删除策略
           
你也可以利用@Sort注解定义一个排序比较器(sort comparator),表明希望的比较器类型,无序、自然顺序或自定义排序,三者择一.若你想用你自己实现的comparator,你还需要利用comparator属性(attribute)指明实现类.
注意你需要使用SortedSet或SortedMap接口
 
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    @JoinColumn(name="CUST_ID")
    @Sort(type = SortType.COMPARATOR, comparator = TicketComparator.class)
    @Where(clause="1=1")
    @OnDelete(action=OnDeleteAction.CASCADE)
    public SortedSet<Ticket> getTickets() {
        return tickets;
    }

关于这些注解更详细的信息,请参阅此前的描述.
Hibernate生成的外键约束的名字可读性相当差,你可以使用@ForeignKey注解覆盖自动生成的值. 注意该注解应该置于关联关系的主体端,inverseName
指向另一端的约束.
@Entity
public class Woman {
    ...
    @ManyToMany(cascade = {CascadeType.ALL})
    @ForeignKey(name = "TO_WOMAN_FK", inverseName = "TO_MAN_FK")
    public Set<Man> getMens() {
        return mens;
    }
}

alter table Man_Woman add constraint TO_WOMAN_FK foreign key (woman_id) references Woman
alter table Man_Woman add constraint TO_MAN_FK foreign key (man_id) references Man
 
比EJB3更胜一筹的是,Hibernate Annotations支持真正的List和Array.
映射集合的方式和以前完全一样,只不过要新增@IndexColumn注解.
该注解允许你指明存放索引值的字段.你还可以定义代表数据库中首个元素的索引值(亦称为索引基数).
常见取值为0或1.
@OneToMany(cascade = CascadeType.ALL)
@IndexColumn(name = "drawer_position", base=1)
public List<Drawer> getDrawers() {
    return drawers;
}

假如你忘了设置@IndexColumn,Hibernate会采用bag语义(译注:即允许重复元素的无序集合).
如果你既想使用bag语义,但是又不希望受制于其约束语义,可以考虑使用@CollectionId注解.
  
Hibernate注解支持true Map映射,如果没有设置@javax.persistence.MapKey,hibernate将key元素或嵌入式对象直接映射到他们所属的列.
要覆写默认的列,可以使用以下注解:
@org.hibernate.annotations.MapKey适用的key为基本类型
(默认为mapkey)或者嵌入式对象,
@org.hibernate.annotations.MapKey适用的key为实体.

双向关联的其中一端在使用@IndexColumn或者    @org.hibernate.annotations.MapKey[ManyToMany]注解需要考虑一些特殊的因素.如果子类存在某个属性映射到索引列,这种情况下是没有问题的,我们可以继续在集合映射的时候使用mappedBy,如下:
@Entity
public class Parent {
    @OneToMany(mappedBy="parent")
    @org.hibernate.annotations.MapKey(columns=@Column(name="name"))
    private Map<String, Child> children;
    ...
}

@Entity
public class Parent {
    ...
    @Basic
    private String name;

    @ManyToOne
    @JoinColumn(name="parent_id", nullable=false)
    private Parent parent;
    ...
}

但是,如果在子类中没有该属性,我们就不能认为这种关联是真正的双向关联(也就是在关联的一端有信息而另一端没有).因此在这种情况下,我们就不能使用mappedBy将其映射集合.取而代之为下面这种映射方式:
@Entity
public class Parent {
    @OneToMany
    @org.hibernate.annotations.MapKey(columns=@Column(name="name"))
    @JoinColumn(name="parent_id", nullable=false)
    private Map<String, Child> children;
    ...
}

@Entity
public class Parent {
    ...
    @ManyToOne
    @JoinColumn(name="parent_id", insertable=false, updatable=false, nullable=false)
    private Parent parent;
    ...
}

注意在上面的映射中,关联的集合端负责更新外键.
另外一个有趣的特征就是可以给bag集合定义一个代理主键.通过这种方式优雅的去除了bag的缺点:update和remove操作更加有效率,每次查询或每个实体可以超过一个EAGER bag.该主键被保存在集合表的一个附加列,该列对于Java应用不可见.@CollectionId注解将一个集合标注为id bag,同时还可以覆写主键列,主键类型以及生成器策略.生成器策略可以是identity,也可以是应用中任何已定义的生成器的名字.

@Entity
@TableGenerator(name="ids_generator", table="IDS")
public class Passport {
    ...

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name="PASSPORT_VISASTAMP")
    @CollectionId(
        columns = @Column(name="COLLECTION_ID"),
        type=@Type(type="long"),
        generator = "ids_generator"
    )
    private Collection<Stamp> visaStamp = new ArrayList();
    ...
}
       
Hibernate Annotations还支持核心类型集合(Integer, String, Enums, ......)、
  可内嵌对象的集合,甚至基本类型数组.这就是所谓的元素集合.
元素集合可用@CollectionOfElements来注解(作为@OneToMany的替代).
为了定义集合表(译注:即存放集合元素的表,与下面提到的主表对应),要在关联属性上使用@JoinTable注解,joinColumns定义了介乎实体主表与集合表之间的连接字段(inverseJoincolumn是无效的且其值应为空).
对于核心类型的集合或基本类型数组,你可以在关联属性上用@Column来覆盖存放元素值的字段的定义.
  你还可以用@AttributeOverride来覆盖存放可内嵌对象的字段的定义.
  要访问集合元素,需要将该注解的name属性值设置为"element"("element"用于核心类型,而"element.serial"用于嵌入式对象的serial属性).要访问集合的index/key,则将该注解的name属性值设置为"key".
@Entity
public class Boy {
    private Integer id;
    private Set<String> nickNames = new HashSet<String>();
    private int[] favoriteNumbers;
    private Set<Toy> favoriteToys = new HashSet<Toy>();
    private Set<Character> characters = new HashSet<Character>();

    @Id @GeneratedValue
    public Integer getId() {
        return id;
    }

    @CollectionOfElements
    public Set<String> getNickNames() {
        return nickNames;
    }

    @CollectionOfElements
    @JoinTable(
            table=@Table(name="BoyFavoriteNumbers"),
            joinColumns = @JoinColumn(name="BoyId")
    )
    @Column(name="favoriteNumber", nullable=false)
    @IndexColumn(name="nbr_index")
    public int[] getFavoriteNumbers() {
        return favoriteNumbers;
    }

    @CollectionOfElements
    @AttributeOverride( name="element.serial", column=@Column(name="serial_nbr") )
    public Set<Toy> getFavoriteToys() {
        return favoriteToys;
    }

    @CollectionOfElements
    public Set<Character> getCharacters() {
        return characters;
    }
    ...
}

public enum Character {
    GENTLE,
    NORMAL,
    AGGRESSIVE,
    ATTENTIVE,
    VIOLENT,
    CRAFTY
}

@Embeddable
public class Toy {
    public String name;
    public String serial;
    public Boy owner;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSerial() {
        return serial;
    }

    public void setSerial(String serial) {
        this.serial = serial;
    }

    @Parent
    public Boy getOwner() {
        return owner;
    }

    public void setOwner(Boy owner) {
        this.owner = owner;
    }


    public boolean equals(Object o) {
        if ( this == o ) return true;
        if ( o == null || getClass() != o.getClass() ) return false;

        final Toy toy = (Toy) o;

        if ( !name.equals( toy.name ) ) return false;
        if ( !serial.equals( toy.serial ) ) return false;

        return true;
    }

    public int hashCode() {
        int result;
        result = name.hashCode();
        result = 29 * result + serial.hashCode();
        return result;
    }
}

在嵌入式对象的集合中,可以使用 @Parent注解嵌入式对象的某属性.
该属性指向该嵌入式对象所属的集合实体.

旧版的Hibernate Annotations用@OneToMany来标识集合元素.
由于语义矛盾,我们引入了@CollectionOfElements注解.
用@OneToMany来标识集合元素的这种旧有方式目前尚有效,
但是不推荐使用,而且在以后的发布版本中不再支持这种方式.
       
为了优化数据库访问,你可以激活所谓的Hibernate二级缓存.该缓存是可以按每个实体和集合进行配置的.
@org.hibernate.annotations.Cache定义了缓存策略及给定的二级缓存的范围.
此注解适用于根实体(非子实体),还有集合.

@Entity
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Forest { ... }

    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    @JoinColumn(name="CUST_ID")
    @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    public SortedSet<Ticket> getTickets() {
        return tickets;
    }

@Cache(
    CacheConcurrencyStrategy usage();
    String region() default "";
    String include() default "all";
)

usage: 给定缓存的并发策略(NONE,READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL)
         
region (可选的):缓存范围(默认为类的全限定类名或是集合的全限定角色名)
         
include (可选的):值为all时包括了所有的属性(proterty),为non-lazy时仅含非延迟属性(默认值为all)
         
Hibernate具有在数据上应用任意过滤器的能力,可在运行期应用于一个给定的session.过滤器需要事先定义好.

@org.hibernate.annotations.FilterDef 或@FilterDefs 定义过滤器声明,为同名过滤器所用.
过滤器声明带有一个name()和一个parameters()数组. 参数提供了在运行时调整过滤器行为的能力,过滤器通过@ParamDef注解定义,该注解包含name和type,你还可以为给定的@FilterDef定义一个defaultCondition()参数,当所有的@Filter中没有任何定义时,可使用该参数定义缺省条件.
@FilterDef (s)可以在类或包一级进行定义.

现在我们来定义应用于实体或集合加载时的SQL过滤器子句.我们使用@Filter,并将其置于实体或集合元素上.

@Entity
@FilterDef(name="minLength", parameters={ @ParamDef( name="minLength", type="integer" ) } )
@Filters( {
    @Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length"),
    @Filter(name="minLength", condition=":minLength <= length")
} )
public class Forest { ... }

当这些集合使用关联表来表示关系的时候,你可能需要对于关联表或者目标实体表应用过滤条件.使用@Filter注解可以在目标实体上添加改类约束.
但是如果你打算在关联表上使用,就需要使用@FilterJoinTable注解.

    @OneToMany
    @JoinTable
    //filter on the target entity table
    @Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length")
    //filter on the association table
    @FilterJoinTable(name="security", condition=":userlevel >= requredLevel")
    public Set<Forest> getForests() { ... }
   
由于Hibernate引入了
      @org.hibernate.annotations.NamedQuery,
      @org.hibernate.annotations.NamedQueries,
      @org.hibernate.annotations.NamedNativeQuery 和
      @org.hibernate.annotations.NamedNativeQueries 命名式查询,
因此Hibernate在命名式查询上比EBJ3规范中所定义的命名式查询提供了更多的特性.
他们在标准版中添加了可作为替代品的一些属性(attributes):
flushMode: 定义查询的刷新模式(Always, Auto, Commit或Never)
cacheable: 查询该不该被缓存
cacheRegion: 若查询已被缓存时所用缓存的范围
fetchSize: 针对该查询的JDBC statement单次获取记录的数目
timeout: 查询超时
callable: 仅用于本地化查询(native query),对于存储过程设为true
comment: 一旦激活注释功能,我们会在向数据库交送查询请求时看到注释
cacheMode: 缓存交护模式(get, ignore,normal,或refresh)
readOnly: 不管是否从查询获取元素都是在只读模式下
       
通过@QueryHint注解可以在
@javax.persistence.NamedQuery
中设置hints.另一个重要的优势是可以将这些注解应用到包上
分享到:
评论

相关推荐

    EJB3.0-JPA实体的注解规范以及Hibernate特有的扩展

    web开发的技术文档 集成了不少web开发框架的API 方便查阅

    EJB3.0实体的注解规范

    EJB3.0实体的注解规范,覆盖了EJB3.0(也就是JPA)实体的注解规范以及Hibernate特有的扩展

    JPA学习笔记-EJB-02JPA属性注解

    上次简单介绍了JPA的基本部署和操作过程,算是认识JPA了,下面我们继续学习JPA吧,我们从JPA的注解标记@Table和@Column开始逐渐介绍。

    ejb3.0 jpa

    JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中,图 1很好地描述了JPA的结构: Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用的对象持久...

    hibernate注解所需包

    使用hibernate注解,必须要使用库hibernate-commons-annotations,hibernate-core,hibernate-jpa,ejb3-persistence,javassist等

    JPA学习笔记-EJB-01JPA初体验

    学习中使用的持久层技术 是 EJB,JPA 注解方式,底层应用的是 Hibernate 实现和 MySql 数据库,应用服务器采 用的是 JBoss 4.2.0。随着笔者的学习笔记,大家一起学习吧,还是那句话,有什么问题 大家一起探讨。有...

    JPA 注解参考文档

    JPA全称Java Persistence API.JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中...同时JPA也是JavaEE5 (EJB) 3.0 规范的组成部分。 这个文档是针对JPA常用注解的一些说明。

    JPA注解参考文档

    JPA全称Java Persistence API.JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中...同时JPA也是JavaEE5 (EJB) 3.0 规范的组成部分。 这个文档是针对JPA常用注解的一些说明。

    hibernate注解工具包

    hibernate注解需要加入4个jar包:hibernate-commons-annotations.jar 、 hibernate-annotations.jar ejb3-persistence.jar 、 hibernate-jpa-2.0-api-1.0.1.Final.jar

    JPA注解详细解释、EJB3、 Hibernate开发

    对于pojo类中标注的详细解释 EJB3、 Hibernate开发

    EJB注解说明

    它们的映射都通过JDK5.0注解来定义(EJB3规范已经定义了对应的XML描述语法). 注解分为两个部分,分别是逻辑映射注解和物理映射注解, 通过逻辑映射注解可以描述对象模型,类之间的关系等等, 而物理映射注解则描述了物理...

    Hibernate注解

    * @content ejb3注解的API定义在javax.persistence.*包里面。 * * 注释说明: * @Entity —— 将一个类声明为一个实体bean(即一个持久化POJO类) * @Id —— 注解声明了该实体bean的标识属性(对应表中的主键)。 * ...

    Hibernate实战(第2版 中文高清版)

    第一部分 从Hibernate和EJB 3.0开始  第1章 理解对象/关系持久化   1.1 什么是持久化   1.1.1 关系数据库   1.1.2 理解SQL   1.1.3 在Java中使用SQL   1.1.4 面向对象应用程序中的持久化   1.2 范式不...

    Hibernate Annotation笔记

    简介: 在过去几年里,Hibernate不断发展,...Hibernate annotation使用了ejb JPA的注解,所以,下面安装配置hibernate annotation环境时,需要导入ejb的包。许多网上的资料都是jpa hibernate annotation方面的资料。

    Hibernate注释大全收藏

    默认情况下,所有属性都用 @Basic 注解。 public transient int counter; //transient property private String firstname; //persistent property @Transient String getLengthInMeter() { ... } //transient ...

    Struts2 + Spring3 + Hibernate3.5 整合(集成测试配套jar包更新构建脚本使用说明)

    本版本全面更新了jar包,全部使用了当前最新版本的jar包,struct2.1.8 spring3 hibernate3.5,全面使用注解取代xm的l配置。 另外增加了一个ant构建脚本,支持使用hudson完成每日构建,持续集成,自动测试,代码规范...

    Manning - Java Persistence With Hibernate(2008).part2.rar

    Hibernian最新书籍,里面有如何使用JPA/EJB3注解.其他书籍很少提到这方面. Manning - Java Persistence With Hibernate(2008).part2.rar 文件太大了,我分成了两部分来传递. 这只是Part2. 完整下载地址: ...

    Manning - Java Persistence With Hibernate(2008).part1.rar

    Hibernian最新书籍,里面有如何使用JPA/EJB3注解.其他书籍很少提到这方面. Manning - Java Persistence With Hibernate(2008).part2.rar 文件太大了,我分成了两部分来传递. 这只是Part1. 完整下载地址: ...

    java必了解的六大问题

    java方向及学习方法 java分成J2ME(移动应用开发),J2SE(桌面应用开发),J2EE(Web企业级应用),... *最后呢,还有些java的技术,包括EJB3.0等,可以选择学习,与三大轻量级框架相比,EJB就是当之无愧的重量级了。

Global site tag (gtag.js) - Google Analytics