EJB3 使用简介

板桥banq

  

1.无态Bean

可以使用普通类作为无态Bean.
定义接口:
public interface Calculator {
  public int calculate (int i, int j);

}
接口实现:
@Stateless //注意这行
public class StatelessCalculator implements Calculator {
……..
}

2.客户端调用JNDI

对于非Seam+ EJB3 需要JNDI访问EJB

JBoss缺省:应用名称(如EAR文件基本名称) + session bean类名 + 是local 或 remote.不推荐
ejb3base.ear 的基本名是ejb3base
ejb3base/StatelessCalculator/local

解耦: 可以在slsb中指定@LocalBinding or @RemoteBinding

3.Ejb3打包注意点


如果出现java.lang.ClassCastException $proxy之类出错,一般是打包classloader方面问题,在war包中不要打入任何WEB-INF/classes下的包。
Jmx-console下service=JNDIView , Output JNDI info as text的Invoke

原来打包结构
EJB3.1简化打包结构
Remote SLSB
@Stateless
@Remote (Calculator.class)
public class RemoteCalculator implements Calculator{…}
Calculator.class是接口类名
Remote client
Context ctx = new InitialContext();
Calculator cal = (Calculator) ctx.lookup("ejb3base/RemoteCalculator/remote");
运行环境classpath加入当前服务器的客户端JAR包Jbossall-client.jar
classpath加入jndi.properties

jndi.properties
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=localhost:1099

4.Local/Remote结合

@Stateless
@Local ({Calculator.class})
@LocalBinding (jndiBinding="EJB3Trail/LocalCalculator")
@Remote ({RemoteCalculator.class})
@RemoteBinding (jndiBinding="EJB3Trail/RemoteCalculator")
public class LocalRemoteCalculator implements Calculator, RemoteCalculator {…}


5.Session bean回调
EJB 3.0允许你将任何方法指定为回调方法 :
@PostConstruct:当bean对象完成实例化后,使用了这个注释的方法会被立即调用。
@PreDestroy:使用这个注释的方法会在容器从它的对象池中销毁一个无用的或者过期的 bean 实例之前调用。


6.无态Bean内部机制POOL
有态会话Bean
@PrePassivate:当一个有状态的session bean实例空闲过长的时间,容器将会钝化(passivate)它,并把它的状态保存在缓存当中。使用这个注释的方法会在容器钝化bean实例之前调用。这个注释适用于有状态的会话bean。当钝化后,又经过一段时间该 bean 仍然没有被操作,容器将会把它从存储介质中删除。以后,任何针对该 bean 方法的调用容器都会抛出例外。

有态会话Bean
@PostActivate:当客户端再次使用已经被钝化的有状态 session bean 时,新的实例被创建,状态被恢复。
使用此注释的session bean会在bean的激活完成时调用。这个注释只适用于有状态的会话 bean。

有态会话Bean
@Init:这个注释指定了有状态session bean初始化的方法。
它区别于@PostConstruct注释在于:
多个@Init注释方法可以同时存在于有状态session bean 中,但每个 bean实例只会有一个@Init注释的方法会被调用。这取决于bean是如何创建的(细节请看EJB 3.0规范)。
@PostConstruct在@Init之后被调用。

有态会话Bean
@Remove,特别是对于有状态 session bean。当应用通过存根对象调用使用了
@Remove注释的方法时,容器就知道在该方法执行完毕后,要把 bean实例从对象池中移走。


7.persistence unit
一组实体,可以划分组,比如普通和管理。
通过persistence.xml配置该单元。
@PersistenceContext(unitName="admin")
EntityManager entityManager;

PersistenceContext表示容器管理EM,由容器负责EM的寻找 打开和关闭
unitName是Persistence.xml中配置

EntityManager
Persistence.xml
<persistence>
<persistence-unit name="admin">
  <jta-data-source>java:/DefaultDS</jta-data-source>
<properties>
  <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>


8.实体Bean

类中加入@Entity
@Table(name = "user")
主键的get方法或字段上加入:
@Id
@GeneratedValue
推荐在方法上加入,符合OO封装。

@Id
每个实体都有一个唯一的ID,这是Domain Model规定
primitives, primitive wrappers Serializable 如String, Date,都可作主键类型,避免数字型
@Id
public Long getId() {return this.id;}
多字段主键使用@IdClass(CategoryPK.class) @EmbeddedId

GeneratedValue
由关系数据库自动产生主键
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="USER_ID")
protected Long userId;
数据表列名USER_ID自动产生值,但是在保存之前userId是没有值。


IDENTITY: 用数据库的identity column
SEQUENCE :用数据库的sequence column
Table 使用表确定唯一性。
SEQUENCE
首先数据库定义sequence :CREATE SEQUENCE user_sequence START WITH 1 INCREMENT BY 10;
@SequenceGenerator(name="USER_SEQUENCE_GENERATOR",
sequenceName="USER_SEQUENCE",
initialValue=1, allocationSize=10)
可以标注在类前或字段或方法前
名称USER_SEQUENCE_GENERATOR是整个持久单元全局有效:
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE,
generator="USER_SEQUENCE_GENERATOR")
@Column(name="USER_ID")
protected Long userId;

table
表结构:CREATE TABLE sequence_generator_table
(sequence_name VARCHAR2(80) NOT NULL,
sequence_value NUMBER(15) NOT NULL,
PRIMARY KEY (sequence_name));

表数据:INSERT INTO
sequence_generator_table (sequence_name, sequence_value)
VALUES ('USER_SEQUENCE', 1);

@TableGenerator (name="USER_TABLE_GENERATOR",
table="SEQUENCE_GENERATOR_TABLE",
pkColumnName="SEQUENCE_NAME",
valueColumnName="SEQUENCE_VALUE",
pkColumnValue="USER_SEQUENCE")
auto
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="USER_ID")
protected Long userId;


Oracle使用SEQUENCE;SQLServer使用IDENTITY TopLink使用table


实体映射Column
@Column(name="USER_ID", null,able=false, length=5)
protected Long userId;
Column是对数据表的定义。
@Column(name="PICTURE", table="USER_PICTURE")
@Lob //Blob大数据
@Basic(fetch=FetchType.LAZY) //懒加载
protected byte[] picture;
@Column(name="USER_ID", insertable=false, updatable=false)
Insertable是在SQL插入时不执行
这两个参数对只读数据有用处。

实体映射@Enumerated
public enum UserType {SELLER, BIDDER, CSR, ADMIN};
UserType. SELLER 的ORDINAL为0; UserType . BIDDER的为ORDINAL 1
@Enumerated(EnumType.ORDINAL)
protected UserType userType
ORDINAL表示枚举类型的值,STRING表示为枚举类型的名称 缺省是值

实体映射@Temporal


java.util.Date or java.util.Calendar
@Temporal(TemporalType.DATE)
protected Date creationDate;
缺省是TIMESTAMP

 


实体映射@Embeddable
适合无主键的值对象,被嵌入其他类中,如User中的Address
Embeddable和嵌入其实体主对象共同拥有一个主键
@Embeddable
public class Address {
protected String streetLine1;
protected String streetLine2;}

@Entity
public class User {
@Embedded
protected Address address;}

Embeddable mapping


Transient
@Transient
protected Long activeUserCount
不希望缓存中的数据保存到数据库,只是一些临时共享数据。


EntityManager
public void persist(Object entity);
public <T> T merge(T entity);相当于update,但是相同部分不更新
public void remove(Object entity);
public <T> T find(Class<T> entityClass, Object primaryKey);
public void flush();和数据库同步
public void refresh(Object entity);从数据库


刷新实体;与merge相反.
EntityManager
public Query createQuery(String jpqlString);JPQL
public Query createNamedQuery(String name);
public void close(); Close an EntityManager.
public boolean isOpen();
public EntityTransaction getTransaction();
public void joinTransaction();


9.实体生命周期
EntityManager管理实体很短时间,不同于EJB2.1CMP在容器关闭才消失。
实体被EntityManager管理称为Managed或attached,方式persist, merge refresh
EM不再管理实体称为detached,这个实体称为transient or new.通过find获得的实体如果没有相关事务场景,立即变成detached
EM在管理实体期间,确保实体数据和数据库一致。自动双向同步,实体概念,数据库也改变。
merge() and refresh()可以detached到detached


Detached三种方法
实体超过EntityManager的scope
EntityManager调用只限于方法的调用,方法调用结束返回后,所有实体就是detached
EntityManager的clear方法
EntityManager remove()
EntityManager scope
TRANSACTION or EXTENDED.缺省是TRANSACTION
@PersistenceContext(type=PersistenceContextType.EXTENDED)
EntityManager entityManager;
在SLSB和MDB中不能用EXTENDED,只能用在有态Bean中。EM将一直管理所有实体,直到有态Bean 被调用remove
特殊点:实体缓存将一直被保存,跨越多个方法调用。

EntityManager TRANSACTION
EntityManager EXTENDED
容器管理EntityManager


如果Web组件直接调用EntityManager ,需要注意线程不安全性,如下调用:
@PersistenceContext(name="pu/actionBazaar",unitName="ActionBazaar")
public class ItemServlet extends HttpServlet {
@Resource private UserTransaction ut;
public void service (HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
  Context ctx = new InitialContext();
  EntityManager entityManager = (EntityManager)
  ctx.lookup("java:comp/env/pu/actionBazaar");
  ut.begin();
  entityManager.persist(item);
  ut.commit();
}
}


Application-Managed EntityManager
Java EE不提供应用管理的EM,需要我们手工写代码,自己控制EM周期
@Stateless
public class ItemManagerBean implements ItemManager {
@PersistenceUnit |#1
private EntityManagerFactory entityManagerFactory;
private EntityManager entityManager;
public ItemManagerBean() {}
@PostConstruct
public void initialize() {
  entityManager = entityManagerFactory.createEntityManager(); |
}
public Item updateItem(Item item) {
  entityManager.joinTransaction(); |
  entityManager.merge(item);
  return item;
}
@PreDestroy
public void cleanup() {
  if (entityManager.isOpen()) {
    entityManager.close(); |
  }
  }
}


实体关系一对一
实体对应的表有一个外键指向子对象
实体关系一对一
@OneToOne(cascade=CascadeType.ALL)
@JoinColumn(name="USER_BILLING_ID",
referencedColumnName="BILLING_ID", updatable=false)
protected BillingInfo billingInfo;
…….
public class BillingInfo {
@Id
@Column(name="BILLING_ID")
protected Long billingId;
...
}
Cascade 是级联,也就是保存或删除父对象连同子对象一起保存或删除。
JoinColum的name是关系表中关系字段名称:指当前表的指向关联子表的外键字段名称,缺省不规定,则该字段名称是"关联字段名称+下划线+指向关联子表的外键字段名称"
referencedColumnName是对方子表的主键名称

 

 

实体关系一对一双向
@Entity
public class BillingInfo {
@OneToOne(mappedBy="billingInfo")
protected User user;
mappedBy 定义的"billingInfo"是指在User实体中使用JoinColumn定义关系字段名称。
主键一对一
@PrimaryKeyJoinColumn


实体关系一对多
一方代码
public class Item {
@OneToMany(mappedBy="item")
protected Set<Bid> bids;
...
}
多方代码
public class Bid {
@ManyToOne
@JoinColumn(name="BID_ITEM_ID"
referencedColumnName="ITEM_ID")
protected Item item;

除了OneToMany ManyToOne之外,还是使用JoinColumn,那么JoinColumn为什么在多方,不是在一方。
因为多方BIDS 表拥有一个指向ITEMS 表的外键foreign key,外键foreign key表达关系,所在方使用JoinColumn
JPA标准不提供单向的一对多,但个别产品:
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="ITEM_ID", referencedColumnName="BID_ITEM_ID")
protected Set<Bid> bids;


实体关系多对一
指向自我的多对一,表达树形关系:
@Id
@Column(name="CATEGORY_ID")
protected Long categoryId;
...
@ManyToOne
@JoinColumn(name="PARENT_ID",
referencedColumnName="CATEGORY_ID")
Category parentCategory;

实体关系多对多
@ManyToMany
@JoinTable(name="CATEGORY_ITEMS",
joinColumns=
@JoinColumn(name="CI_CATEGORY_ID",
referencedColumnName="CATEGORY_ID"),
inverseJoinColumns=
@JoinColumn(name="CI_ITEM_ID",
referencedColumnName="ITEM_ID"))
protected Set<Item> items

...
@ManyToMany(mappedBy="items")
protected Set<Category> categories;


继承关系在同一表
@Entity
@Table(name="USERS")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="USER_TYPE",
discriminatorType=DiscriminatorType.STRING, length=1)
public abstract class User ...

@Entity
@DiscriminatorValue(value="S")
public class Seller extends User ...

@Entity
@DiscriminatorValue(value="B")
public class Bidder extends User


继承在各自表
@Entity
@Table(name="USERS")
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
|#2
public class User {
...
@Entity
@Table(name="SELLERS")
public class Seller extends User {
...
@Entity
@Table(name="BIDDERS")
public class Bidder extends User {


10.JPQL
named 和 dynamic.
Named为了二次重用,提高性能 比如一些查询需要反复被调用
Dynamic则是动态根据查询条件
@PersistentContext em; |#1
...
public List findAllCategories() {
Query query = em.createQuery("SELECT c FROM Category c"); …
return query.getResultList();

Named查询则是em.createNamedQuery

Named query
@Entity
@NamedQuery(name = "findAllCategories",
query = "SELECT c FROM Category c WHERE c.categoryName
LIKE :categoryName ")
public class Category implements Serializable

Query query = em.createNamedQuery("findAllCategories")

Query接口
query = em.createNamedQuery("findCategoryByName");
query.setParameter("categoryName", categoryName);
query.setMaxResults(10);//最大返回结果10个
query.setFirstResult(3);//从返回结果的初始位置3开始 可分页
List categories = query.getResultList();

SELECT i FROM Item i WHERE i.initialPrice = 1
query.setParameter(1, 100.00);

SELECT i FROM Item i WHERE i.initialPrice = :price
query.setParameter("price", 100.00);


WHERE CONCAT(u.firstName, u.lastName) = 'ViperAdmin'

WHERE SUBSTRING(u.lastName, 1, 3) = 'VIP'

算术
ABS(simple_arithmetic_expression) :绝对值
SQRT(simple_arithmetic_expression):返回
SIZE(collection_value_path_expression) :返回集合大小
临时
CURRENT_DATE
CURRENT_TIME
CURRENT_TIMESTAMP
聚合结果
SELECT MAX(i.itemPrice) FROM Item i
GROUP BY / ORDER
SELECT c.user, COUNT(c.categoryId)
FROM Category c GROUP BY c.user

SELECT c FROM Category c
ORDER BY c.categoryName ASC


注解和ejb-jar.xml对照

EJB拦截器
@Interceptors(MyInterceptor.class)
public String getResult(){
return "hello";
}

对当前方法getResult进行拦截。
EJB拦截器
@AroundInvoke
public Object dosomething(InvocationContext invocationContext) throws Exception {
System.out.println("Entering method: "
+ invocationContext.getMethod().getName());
return invocationContext.proceed();
}


Timer
@Resource TimerService timerService;
...
public void addBid(Bid bid) {//开始计时
timerService.createTimer(15*60*1000,
15*60*1000, bid);//每隔15分钟
}
initial timeout :初次tomeout interval durations:间隔

@Timeout
public void monitorBid(Timer timer) {
Bid bid = (Bid) timer.getInfo();
}
TimerService接口
public Timer createTimer(long duration,
java.io.Serializable info);
public Timer createTimer(long initialDuration,
long intervalDuration, java.io.Serializable info);
public Timer createTimer(java.util.Date expiration,
java.io.Serializable info);
public Timer createTimer(java.util.Date initialExpiration,
long intervalDuration, java.io.Serializable info);
public Collection getTimers();


JMS 客户端
@Resource(name="jms/QueueConnectionFactory")
private ConnectionFactory connectionFactory;
@Resource(name="jms/ShippingRequestQueue")
private Destination destination;


Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(true,
Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(destination);
ObjectMessage message = session.createObjectMessage();
ShippingRequest shippingRequest = new ShippingRequest();
message.setObject(shippingRequest);
producer.send(message);
session.close(); |#10
connection.close();


11.MDB


@MessageDriven(
name="ShippingRequestProcessor",
activationConfig = {
@ActivationConfigProperty(
propertyName="destinationType",
propertyValue="javax.jms.Queue"),
@ActivationConfigProperty(
propertyName="destinationName",
propertyValue="jms/ShippingRequestQueue")
}
)
public class ShippingRequestProcessorMDB implements MessageListener

@Resource
private MessageDrivenContext context;
public void onMessage(Message message) {
try {
  ObjectMessage objectMessage = (ObjectMessage)message;
  ShippingRequest shippingRequest =
      (ShippingRequest)objectMessage.getObject();
  processShippingRequest(shippingRequest);
} catch (JMSException jmse) {
  jmse.printStackTrace();
  context.setRollBackOnly();
} catch (SQLException sqle) {
  sqle.printStackTrace();
  context.setRollBackOnly();
}
}