`
收藏列表
标题 标签 来源
Join用法,HQL的方法,Hibernate中的fetch
以下文字摘自:JOIN,  JOIN2, HQL, Fetch

Join用法:

主要有Inner Join 及 Outer Join:

 

最常用的(默认是Inner):

Select <要选择的字段> From <主要资料表>

  <Join 方式> <次要资料表> [On <Join 规则>] 

Inner Join 的主要精神就是 exclusive , 叫它做排他性吧! 就是讲 Join 规则不相符的资料就会被排除掉, 譬如讲在 Product 中有一项产品的供货商代码 (SupplierId), 没有出现在 Suppliers 资料表中, 那么这笔记录便会被排除掉 

  

Outer Join: 

Select <要查询的字段> From <Left 资料表> 

  <Left | Right> [Outer] Join <Right 资料表> On <Join 规则> 

  

语法中的 Outer 是可以省略的, 例如你可以用 Left Join 或是 Right Join, 在本质上, Outer Join 是 inclusive, 叫它做包容性吧! 不同于 Inner Join 的排他性, 因此在 Left Outer Join 的查询结果会包含所有 Left 资料表的资料, 颠倒过来讲, Right Outer Join 的查询就会包含所有 Right 资料表的资料 

  

另外,还有全外联: 

FULL JOIN 或 FULL OUTER JOIN 


完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。 

  

以及, 

交叉联接 

交叉联接返回左表中的所有行,左表中的每一行与右表中的所有行组合。交叉联接也称作笛卡尔积。 

没有 WHERE 子句的交叉联接将产生联接所涉及的表的笛卡尔积。第一个表的行数乘以第二个表的行数等于笛卡尔积结果集的大小。也就是说在没有 WHERE 子句的情况下,若表 A 有 3 行记录,表 B 有 6 行记录 : : 

SELECT A.*,B.* FROM 表A CROSS JOIN 表B 

那以上语句会返回 18 行记录。 

  

  

Fetch: 

在我们查询Parent对象的时候,默认只有Parent的内容,并不包含childs的信息,如果在Parent.hbm.xml里设置lazy="false"的话才同时取出关联的所有childs内容.
问题是我既想要hibernate默认的性能又想要临时的灵活性该怎么办?  这就是fetch的功能。我们可以把fetch与lazy="true"的关系类比为事务当中的编程式事务与声明式事务,不太准确,但是大概是这个意思。
总值,fetch就是在代码这一层给你一个主动抓取得机会. 

Parent parent = (Parent)hibernateTemplate.execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
                Query q = session.createQuery(
"from Parent as parent "+
" left outer join fetch parent.childs " +
" where parent.id = :id"
                );
                q.setParameter("id",new Long(15));
return (Parent)q.uniqueResult();
            }
        });
        Assert.assertTrue(parent.getChilds().size() > 0); 

你可以在lazy="true"的情况下把fetch去掉,就会报异常. 当然,如果lazy="false"就不需要fetch了 

  

  

HQL一些特色方法: 

in and between may be used as follows: 

from DomesticCat cat where cat.name between 'A' and 'B'
from DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' )
and the negated forms may be written 

from DomesticCat cat where cat.name not between 'A' and 'B'
from DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )
Likewise, is null and is not null may be used to test for null values. 

Booleans may be easily used in expressions by declaring HQL query substitutions in Hibernate configuration: 

<property name="hibernate.query.substitutions">true 1, false 0</property>
This will replace the keywords true and false with the literals 1 and 0 in the translated SQL from this HQL: 

from Cat cat where cat.alive = true
You may test the size of a collection with the special property size, or the special size() function. 

from Cat cat where cat.kittens.size > 0
from Cat cat where size(cat.kittens) > 0
For indexed collections, you may refer to the minimum and maximum indices using minindex and maxindex functions. Similarly, you may refer to the minimum and maximum elements of a collection of basic type using the minelement and maxelement functions. 

from Calendar cal where maxelement(cal.holidays) > current_date
from Order order where maxindex(order.items) > 100
from Order order where minelement(order.items) > 10000
The SQL functions any, some, all, exists, in are supported when passed the element or index set of a collection (elements and indices functions) or the result of a subquery (see below). 

select mother from Cat as mother, Cat as kit
where kit in elements(foo.kittens)
select p from NameList list, Person p
where p.name = some elements(list.names)
from Cat cat where exists elements(cat.kittens)
from Player p where 3 > all elements(p.scores)
from Show show where 'fizard' in indices(show.acts)
Note that these constructs - size, elements, indices, minindex, maxindex, minelement, maxelement - may only be used in the where clause in Hibernate3. 

Elements of indexed collections (arrays, lists, maps) may be referred to by index (in a where clause only): 

from Order order where order.items[0].id = 1234
select person from Person person, Calendar calendar
where calendar.holidays['national day'] = person.birthDay
    and person.nationality.calendar = calendar
select item from Item item, Order order
where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11
select item from Item item, Order order
where order.items[ maxindex(order.items) ] = item and order.id = 11
The expression inside [] may even be an arithmetic expression. 

select item from Item item, Order order
where order.items[ size(order.items) - 1 ] = item
HQL also provides the built-in index() function, for elements of a one-to-many association or collection of values. 

select item, index(item) from Order order 
    join order.items item
where index(item) < 5
Scalar SQL functions supported by the underlying database may be used 

from DomesticCat cat where upper(cat.name) like 'FRI%'
异常设计
异常是面向对象语言非常重要的一个特性,良好的异常设计对程序的可扩展性、可维护性、健壮性都起到至关重要。 
JAVA根据用处的不同,定义了两类异常 
    * Checked Exception: Exception的子类,方法签名上需要显示的声明throws,编译器迫使调用者处理这类异常或者声明throws继续往上抛。 
    * Unchecked Exception: RuntimeException的子类,方法签名不需要声明throws,编译器也不会强制调用者处理该类异常。 

异常的作用和好处: 
1. 分离错误代码和正常代码,代码更简洁。 
2. 保护数据的正确性和完整性,程序更严谨。 
3. 便于调试和排错,软件更好维护。 
…… 

相信很多JAVA开发人员都看到或听到过“不要使用异常来控制流程”,虽然这句话非常易于记忆,但是它并未给出“流程”的定义,所以很难理解作者的本意,让人迷惑不解。 

如果“流程”是包括程序的每一步执行,我认为异常就是用来控制流程的,它就是用来区分程序的正常流程和错误流程,为了更能明确的表达意思,上面这句话应改成“不要用异常来控制程序的正常流程”。现在带来一个新的问题就是如何区分程序正常流程和异常流程?我实在想不出一个评判标准,就举例来说明,大家思维扩散下。 

为了后面更方便的表达,我把异常分成两类,不妥之处请谅解 

    * 系统异常:软件的缺陷,客户端对此类异常是无能为力的,通常都是Unchecked Exception。 
    *业务异常:用户未按正常流程操作导致的异常,都是Checked Exception 

金币转帐例子 
1. 需求规定金币一次的转账范围是1~500,如果超过这个额度,就要提示用户金额超出单笔转账的限制,转账的金额是由用户在页面输入的: 
因为值是用户输入的,所以给的值超出限定的范围肯定是司空见惯。我们当然不能把它(输入的值超出限定的范围)归结于异常流程,它应该属于正常流程,应该提供验证数据的完整功能。 
正确的实现如下: 
提供一个判断转账金币数量是否超出限定范围的方法 


Java代码  
1.private static final int MAX_PRE_TRANSFER_COIN = 500;   
2.  
3.public boolean isCoinExceedTransferLimits(int coin) {   
4.return coin > MAX_PRE_TRANSFER_COIN;   
5.}  
private static final int MAX_PRE_TRANSFER_COIN = 500;

public boolean isCoinExceedTransferLimits(int coin) {
return coin > MAX_PRE_TRANSFER_COIN;
}

Action里先对值进行校验,若不合法,直接返回并提示用户 

2. 在转账的过程里,发些金币数量不够: 
我们的程序都是运行在并发环境中,Action无法完全做到判断金币是否足够。因为在判断之后和事务之前的刹那间,有可能产生其他扣费操作导致金币不够。这时我们就需要用业务异常(Checked Exception)来控制 

正确的实现如下 
CoinNotEnoughExcetion .java 

Java代码  
1.//金币不够的异常类   
2.public class CoinNotEnoughExcetion extends Exception {   
3.private static final long serialVersionUID = -7867713004171563795L;   
4.private int coin;   
5.public CoinNotEnoughExcetion() {   
6.}   
7.  
8.public CoinNotEnoughExcetion(int coin) {   
9.this.coin = coin;   
10.}   
11.  
12.public int getCoin() {   
13.return coin;   
14.}   
15.  
16.@Override  
17.public String getMessage() {   
18.return coin + " is exceed transfer limit:500";   
19.}   
20.}  
//金币不够的异常类
public class CoinNotEnoughExcetion extends Exception {
private static final long serialVersionUID = -7867713004171563795L;
private int coin;
public CoinNotEnoughExcetion() {
}

public CoinNotEnoughExcetion(int coin) {
this.coin = coin;
}

public int getCoin() {
return coin;
}

@Override
public String getMessage() {
return coin + " is exceed transfer limit:500";
}
}

//转账方法 

Java代码  
1.private static final int MAX_PRE_TRANSFER_COIN = 500;   
2.  
3.public void transferCoin(int coin) throws CoinNotEnoughExcetion{   
4.if (!hasEnoughCoin())   
5.throw new CoinNotEnoughExcetion(coin);   
6.// do transfering coin   
7.}  
private static final int MAX_PRE_TRANSFER_COIN = 500;

public void transferCoin(int coin) throws CoinNotEnoughExcetion{
if (!hasEnoughCoin())
throw new CoinNotEnoughExcetion(coin);
// do transfering coin
}


3. 接口transferCoin(int coin)的规范里已经定了契约,调用transferCoin之前必须要先调用isCoinExceedTransferLimits判断值是否合法: 
虽然规范人人都要遵循,但毕竟只是规范,编译器无法强制约束。此时就需要用系统异常(Unchecked Exception,用JDK的标准异常)来保证程序的正确性,没遵守规范的都当做软件bug处理。 
正确的实现如下: 

//转账方法 

Java代码  
1.public void transferCoin(int coin){   
2.if (coin > MAX_PRE_TRANSFER_COIN)   
3.throw new IllegalArgumentException(coin+" is exceed tranfer limits:500");   
4.// do transfering coin   
5.}  
public void transferCoin(int coin){
if (coin > MAX_PRE_TRANSFER_COIN)
throw new IllegalArgumentException(coin+" is exceed tranfer limits:500");
// do transfering coin
}

至此,举例已经结束了,在这里再延伸下—业务异常和系统异常类在实现上的区别,该区别的根源来自于调用者捕获到异常后是如何处理的 

Action对业务异常的处理:操作具体的异常类 


Java代码  
1.public String execute() {   
2.try {   
3.userService.transferCoin(coin);   
4.} catch (CoinExceedTransferLimitExcetion e) {   
5.e.getCoin();   
6.}   
7.return SUCCESS;   
8.}  
public String execute() {
try {
userService.transferCoin(coin);
} catch (CoinExceedTransferLimitExcetion e) {
e.getCoin();
}
return SUCCESS;
}

Action对系统异常的处理:无法知道具体的异常类 

Java代码  
1.public String execute() {   
2.try {   
3.userService.transferCoin(coin);   
4.} catch (RuntimeException e) {   
5.LOG.error(e.getMessage());   
6.}   
7.return SUCCESS;   
8.}  
public String execute() {
try {
userService.transferCoin(coin);
} catch (RuntimeException e) {
LOG.error(e.getMessage());
}
return SUCCESS;
}

调用者捕获业务异常(Checked Excetion)之后,通常不会去调用getMessage()方法的,而是调用异常类里特有的方法,所以业务异常类的实现要注重特有的,跟业务相关的方法,而不是getMessage()方法。 
系统异常类恰恰相反,捕获者只会调用getMessage()获取异常信息,然后记录错误日志,所以系统异常类(Uncheck Exception)的实现类对getMessage()方法返回内容比较讲究。 

不管是业务异常还是系统异常,都需要提供丰富的信息。如:数据库访问异常,需要提供查询sql语句等;HTTP接口调用异常,需要给出访问的URL和参数列表(如果是post请求,参数列表不提供也可以,取决于维护人员或者开发人员拿到参数列表会如何去使用)。 
1. Sql语法错误异常类(取自spring框架的异常类,Spring的异常体系很强大,值得一看): 


Java代码  
1.public class BadSqlGrammarException extends InvalidDataAccessResourceUsageException {   
2.private String sql;   
3.  
4.public BadSqlGrammarException(String task, String sql, SQLException ex) {   
5.super(task + "; bad SQL grammar [" + sql + "]", ex);   
6.this.sql = sql;   
7.}   
8.  
9.public SQLException getSQLException() {   
10.return (SQLException) getCause();   
11.}   
12.  
13.public String getSql() {   
14.return this.sql;   
15.}   
16.}  
public class BadSqlGrammarException extends InvalidDataAccessResourceUsageException {
private String sql;

public BadSqlGrammarException(String task, String sql, SQLException ex) {
super(task + "; bad SQL grammar [" + sql + "]", ex);
this.sql = sql;
}

public SQLException getSQLException() {
return (SQLException) getCause();
}

public String getSql() {
return this.sql;
}
}

2. HTTP接口调用异常类 


Java代码  
1.public class HttpInvokeException extends RuntimeException {   
2.private static final long serialVersionUID = -6477873547070785173L;   
3.  
4.public HttpInvokeException(String url, String message) {   
5.super("http interface unavailable [" + url + "];" + message);   
6.}   
7.}  
public class HttpInvokeException extends RuntimeException {
private static final long serialVersionUID = -6477873547070785173L;

public HttpInvokeException(String url, String message) {
super("http interface unavailable [" + url + "];" + message);
}
}

如何选择用Unchecked Exception和Checked Exception 
1.是软件bug还是业务异常,软件bug是Unchecked Exception,否则是Checked Exception 
2.如果把该异常抛给用户,用户能否做出补救。如果客户端无能为力,则用Unchecked Exception,否则抛Checked Exception 
结合这两点,两类异常就不会混淆使用了。 


异常设计的几个原则: 
1.如果方法遭遇了一个无法处理的意外情况,那么抛出一个异常。 
2.避免使用异常来指出可以视为方法的常用功能的情况。 
3.如果发现客户违反了契约(例如,传入非法输入参数),那么抛出非检查型异常。 
4.如果方法无法履型契约,那么抛出检查型异常,也可以抛出非检查型异常。 
5.如果你认为客户程序员需要有意识地采取措施,那么抛出检查型异常。 
6.异常类应该给客户提供丰富的信息,异常类跟其它类一样,允许定义自己的属性和方法。 
7.异常类名和方法遵循JAVA类名规范和方法名规范 
8.跟JAVA其它类一样,不要定义多余的方法和变量。(不会使用的变量,就不要定义,spring的BadSqlGrammarException.getSql() 就是多余的) 

以下是我工作当中碰到的一些我认为不是很好的写法,我之前也犯过此类的错误 
A. 整个业务层只定义了一个异常类 

Java代码  
1.public class ServiceException extends RuntimeException {   
2.private static final long serialVersionUID = 8670135969660230761L;   
3.  
4.public ServiceException(Exception e) {   
5.super(e);   
6.}   
7.  
8.public ServiceException(String message) {   
9.super(message);   
10.}   
11.}  
public class ServiceException extends RuntimeException {
private static final long serialVersionUID = 8670135969660230761L;

public ServiceException(Exception e) {
super(e);
}

public ServiceException(String message) {
super(message);
}
}

理由: 
1.业务异常不应该是Unchecked Exception。 
2.不存在一个具体的异常类名称是“ServiceException”。 

解决方法:定义一个抽象的业务异常“ServiceException” 

Java代码  
1.public abstract class ServiceException extends Exception {   
2.private static final long serialVersionUID = -8411541817140551506L;   
3.}  
public abstract class ServiceException extends Exception {
private static final long serialVersionUID = -8411541817140551506L;
}

B. 忽略异常 

Java代码  
1.try {   
2.new String(source.getBytes("UTF-8"), "GBK");   
3.} catch (UnsupportedEncodingException e) {   
4.e.printStackTrace();   
5.}  
try {
new String(source.getBytes("UTF-8"), "GBK");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}

理由: 
1.环境不支持UTF-8或者GBK很显然是一个非常严重的bug,不能置之不理 
2.堆栈的方式记录错误信息不合理,若产品环境是不记录标准输出,这个错误信息就会丢失掉。若产品环境是记录标准输出,万一这段程序被while循环的线程调用,有可能引起硬盘容量溢出,最终导致程序的运行不正常,甚至数据的丢失。 

解决方法:捕获UnsupportedEncodingException,封装成Unchecked Exception,往上抛,中断程序的执行。 

Java代码  
1.try {   
2.new String(source.getBytes("UTF-8"), "GBK");   
3.} catch (UnsupportedEncodingException e) {   
4.throw new IllegalStateException("the base runtime environment does not support 'UTF-8' or 'GBK'");   
5.}  
try {
new String(source.getBytes("UTF-8"), "GBK");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("the base runtime environment does not support 'UTF-8' or 'GBK'");
}

C. 捕获顶层的异常—Exception 

Java代码  
1.public void transferCoin(int outUid, int inUserUid, int coin) throws CoinNotEnoughException {   
2.final User outUser = userDao.getUser(outUid);   
3.final User inUser = userDao.getUser(inUserUid);   
4.outUser.decreaseCoin(coin);   
5.inUser.increaseCoin(coin);   
6.try {   
7.// BEGIN TRANSACTION   
8.userDao.save(outUser);   
9.userDao.save(inUser);   
10.// END TRANSACTION   
11.// log transfer operate   
12.} catch (Exception e) {   
13.throw new ServiceException(e);   
14.}   
15.}  
public void transferCoin(int outUid, int inUserUid, int coin) throws CoinNotEnoughException {
final User outUser = userDao.getUser(outUid);
final User inUser = userDao.getUser(inUserUid);
outUser.decreaseCoin(coin);
inUser.increaseCoin(coin);
try {
// BEGIN TRANSACTION
userDao.save(outUser);
userDao.save(inUser);
// END TRANSACTION
// log transfer operate
} catch (Exception e) {
throw new ServiceException(e);
}
}

理由: 
1. Service并不是只能抛出业务异常,Service也可以抛出其他异常 
如IllegalArgumentException、ArrayIndexOutOfBoundsException或者spring框架的DataAccessException 
2. 多数情况下,Dao不会抛Checked Exception给Service,假如所有代码都非常规范,Service类里不应该出现try{}catch代码。 

解决方法:删除try{}catch代码 

Java代码  
1.public void transferCoin(int outUid, int inUserUid, int coin) throws CoinNotEnoughException {   
2.final User outUser = userDao.getUser(outUid);   
3.final User inUser = userDao.getUser(inUserUid);   
4.outUser.decreaseCoin(coin);   
5.inUser.increaseCoin(coin);   
6.// BEGIN TRANSACTION   
7.userDao.save(outUser);   
8.userDao.save(inUser);   
9.// END TRANSACTION   
10.// log transfer operate   
11.}  
public void transferCoin(int outUid, int inUserUid, int coin) throws CoinNotEnoughException {
final User outUser = userDao.getUser(outUid);
final User inUser = userDao.getUser(inUserUid);
outUser.decreaseCoin(coin);
inUser.increaseCoin(coin);
// BEGIN TRANSACTION
userDao.save(outUser);
userDao.save(inUser);
// END TRANSACTION
// log transfer operate
}

D. 创建没有意义的异常 

Java代码  
1.public class DuplicateUsernameException extends Exception {   
2.}  
public class DuplicateUsernameException extends Exception {
}
理由 
1. 它除了有一个"意义明确"的名字以外没有任何有用的信息了。不要忘记Exception跟其他的Java类一样,客户端可以调用其中的方法来得到更多的信息。 

解决方案:定义上捕获者需要用到的信息 

Java代码  
1.public class DuplicateUsernameException extends Exception {   
2.private static final long serialVersionUID = -6113064394525919823L;   
3.private String username = null;   
4.private String[] availableNames = new String[0];   
5.  
6.public DuplicateUsernameException(String username) {   
7.this.username = username;   
8.}   
9.  
10.public DuplicateUsernameException(String username, String[] availableNames) {   
11.this(username);   
12.this.availableNames = availableNames;   
13.}   
14.  
15.public String requestedUsername() {   
16.return this.username;   
17.}   
18.  
19.public String[] availableNames() {   
20.return this.availableNames;   
21.}   
22.}  
public class DuplicateUsernameException extends Exception {
private static final long serialVersionUID = -6113064394525919823L;
private String username = null;
private String[] availableNames = new String[0];

public DuplicateUsernameException(String username) {
this.username = username;
}

public DuplicateUsernameException(String username, String[] availableNames) {
this(username);
this.availableNames = availableNames;
}

public String requestedUsername() {
return this.username;
}

public String[] availableNames() {
return this.availableNames;
}
}

E. 把展示给用户的信息直接放在异常信息里。 

Java代码  
1.public class CoinNotEnoughException2 extends Exception {   
2.private static final long serialVersionUID = 4724424650547006411L;   
3.  
4.public CoinNotEnoughException2(String message) {   
5.super(message);   
6.}   
7.}   
8.  
9.public void decreaseCoin(int forTransferCoin) throws CoinNotEnoughException2 {   
10.if (this.coin < forTransferCoin)   
11.throw new CoinNotEnoughException2("金币数量不够");   
12.this.coin -= forTransferCoin;   
13.}  
public class CoinNotEnoughException2 extends Exception {
private static final long serialVersionUID = 4724424650547006411L;

public CoinNotEnoughException2(String message) {
super(message);
}
}

public void decreaseCoin(int forTransferCoin) throws CoinNotEnoughException2 {
if (this.coin < forTransferCoin)
throw new CoinNotEnoughException2("金币数量不够");
this.coin -= forTransferCoin;
}

理由:展示给用户错误提示信息属于文案范畴,文案易变动,最好不要跟程序混淆一起。 
解决方法: 
错误提示的文案统一放在一个配置文件里,根据异常类型获取对应的错误提示信息,若需要支持国际化还可以提供多个语言的版本。 

F. 方法签名声明了多余的throws 
理由:代码不够精简,调用者不得不加上try{}catch代码 
解决方案:若方法不可能会抛出该异常,那就删除多余的throws 

G. 给每一个异常类都定义一个不会用到ErrorCode 
理由:多一个功能就多一个维护成本 
解决方法:不要无谓的定义ErrorCode,除非真的需要(如给别人提供接口调用的,这时,最好对异常进行规划和分类。如1xx代表金币相关的异常,2xx代表用户相关的异常… 

最后推荐几篇关于异常设计原则 
1.异常设计 
http://www.cnblogs.com/JavaVillage/articles/384483.html(翻译) 
http://www.javaworld.com/jw-07-1998/jw-07-techniques.html(原文) 

2. 异常处理最佳实践 
http://tech.e800.com.cn/articles/2009/79/1247105040929_1.html(翻译) 
http://onjava.com/pub/a/onjava/2003/11/19/exceptions.html(原文)
ctm
EJB 3事务之容器管理事务(CMT)BEAN管理事务2010-02-02 16:20容器管理事务(Container-Managed Transaction, CMT ):容器管理事务允许组件自动征集(enlist )到事务中,也就是说,EJB 组件从不需要显式地给出begin 、commit 、abort 语句,EJB 容器会替开发者考虑这些内容。EJB 容器会依据EJB 组件提供者指定的事务行为来界定相应的事务边界。在使用容器管理事务时,EJB 容器会拦截客户请求,并自动为EJB 组建启动新的事务,也就是说,容器会通过begin 语句调用底层事务系统,从而启动事务。随后,容器会将业务请求委派给EJB 组件,组件中的业务操作将运行在这一事务中。处于事务中的EJB 组件能够执行任何业务逻辑,如写入数据库、发送异步信息、调用其他的EJB 组件等。一旦在处理业务过程中出现问题,则EJB 组建需要通知EJB 容器去回滚事务。当EJB 组建完成业务处理后,会将控制权交回给EJB 容器。随后,EJB 容器能够通过commit 或abort 语句调用底层事务系统。

通过使用@TransactionAttribute 注释或部署描述符,开发者能够指定事务属性。EJB 容器通过分析事务属性便能够知道如何处理EJB 组件的事务需求。EJB 事务属性的取值有:

(1 )Required ,如果EJB 组件必须总是运行在事务中,则应该使用Required 模式。如果已经有事务在运行,则EJB 组件参与其中;如果没有事务运行,则EJB 容器会为EJB 组件启动新的事务。

Required 是默认和最常使用的事务属性值。这个值指定必须在事务之内调用EJB 方法。如果从非事务性客户端调用方法,那么容器会在调用方法之前开始事务,并且在方法返回时结束事务。另一方面,如果调用者从事务性上下文调用方法,那么方法会联结已有事务。在从客户段传播事务的情况下,如果我们的方法表示应该回滚事务,那么容器不仅回回滚整个事务,而且会向客户端抛出异常,从而让客户端知道它开始的事务已经被另一个方法回滚了。

(2 )Requires_New ,当客户调用EJB 时,如果总是希望启动新的事务,则应该使用RequiresNew 事务属性,如果客户在调用EJB 组件时已经存在事务,则当前事务会被挂起,进而容器启动新的事务,并将调用请求委派给EJB 组件。也就是说,如果客户端已经有了事务,那么它暂停该事务,知道方法返回位置,新事务是成功还是失败都不会影响客户端已有的事务。EJB 组件执行相应的业务操作,容器会提交或回滚事务,最终容器将恢复原有的事务,当然,如果客户在调用EJB 组件时不存在事务,则不需要执行事务的挂起或恢复操作。

RequiresNew 事务属性非常有用。如果EJB 组件需要事务的ACID 属性,并且将EJB 组件运行在单个独立的工作单元中,从而不会将其他外部逻辑也包括在当前的事务中,则必须使用RequiredNew 事务属性。如果需要事务,但是不希望事务的回滚影响客户端,就应该使用它。另外,当不希望客户端的回滚影响你的时候,也应该使用这个值。日志记录是个很好的例子,即使父事务回滚,你也希望把错误情况记录到日志中,另一方面,日志记录细小调试信息的失败不应该导致回滚整个事务,并且问题应该仅限于日志记录组件内。

(3 )Supports ,如果某个EJB 组件使用了Supports 事务属性,则只有调用它的客户已经启用了事务时,这一EJB 组件才会运行在事务中。如果客户并没有运行在事务中,则EJB 组建也不会运行在事务中。Supports 同Required 事务属性很相似,但是,Required 要求EJB 组件必须运行在事务中。如果使用Support 事务属性,EJB 组建很可嫩没有运行在事务中。

(4 )Mandatory ,Mandatory 事务属性要求调用EJB 组件的客户必须已经运行在事务中。如果从非事务性客户端调用使用Mandatory 属性的EJB 方法,那么客户将接受到系统抛出的javax.ejb.EJBTransactionRequiredException 异常。EJB 组件使用Mandatory 事务属性是非常安全的,它能够保证EJB 组建运行在事务中。如果客户没有运行在事务中,则不能够调用到应用了Mandatory 事务属性的EJB 组件。但是,Mandatory 事务属性要求第3 方(及客户)在调用EJB 组件前必须启动了事务。EJB 容器并不会为Mandatory 事务属性自动启动新事务,这是同Support 事务属性的最主要区别。

(5 )NotSupported ,如果EJB 组件使用了NotSupport 事务属性,它根本不会参与到事务中。如果调用者使用相关联的事务调用方法,容器就会暂停事务,调用方法,然后再方法返回时恢复事务。通常,此属性只用于非实物性的自动确认模式中,支持JMS 提供者的MDB 。

(6 )Never ,如果EJB 组件使用Never 事务属性,它就不能够参与到事务中,而且,如果调用它的客户已经处于事务中,则容器会将javax.ejb.EJBException 异常抛给客户。

事务效果图,其中,T1 和T2 是2 个不同的事务,T1 是客户请求传递的事务,T2 是容器启动的事务,通过下表,能够理解各种事务属性在影响事务长度和范围方面所起的重要作用。

 


使用CMT 实现Transaction :

view plaincopy to clipboardprint?
import javax.annotation.Resource;   
import javax.ejb.SessionContext;   
import javax.ejb.Stateless;   
import javax.ejb.TransactionManagement;   
import javax.ejb.TransactionManagementType;   
import javax.ejb.TransactionAttribute;   
import javax.ejb.TransactionAttributeType;   
@Stateless 
@TransactionManagement(TransactionManagementType.CONTAINER)   
public class OrderManagerBean {   
        @Resource 
        private SessionContext context;   
           
        @TransactionAttribute(TransactionAttributeType.REQUIRED)   
        public void placeSnagItOrder(Item item, Customer customer) {   
                try{   
                        if(bidsExisting(item)) {   
                                validateCredit(customer);   
                                chargeCustomer(customer, item);   
                                removeItemFromBidding(item);   
                        }   
                } catch(Exception e) {   
                        context.setRollbackOnly();   
                }   
        }   
} 

CMT 的最大优势也是其最大弱点是,使用CMT ,会限制你将事务边界设置在业务方法开始和结束的位置,依靠容器决定何时开始、提交和回滚事务。另一方面,BMT 允许你通过变成确切指定这些细节,使用与JDBC 事务模型类似的语义。

例子:

view plaincopy to clipboardprint?
import javax.annotation.Resource;   
import javax.ejb.SessionContext;   
import javax.ejb.Stateless;   
import javax.ejb.TransactionManagement;   
import javax.ejb.TransactionManagementType;   
import javax.transaction.UserTransaction;   
@Stateless 
@TransactionManagement(TransactionManagementType.BEAN)   
public class OrderManagerBean {   
           
        @Resource 
        private SessionContext context;   
           
        public void placeSnagItOrder(Item item, Customer customer) {   
                UserTransaction userTransaction = context.getUserTransaction();   
                try{   
                        userTransaction.begin();   
                        if(!bidsExisting(item)) {   
                                validateCredit(customer);   
                                chargeCustomer(customer, item);   
                                removeItemFromBidding(item);   
                        }   
                        userTransaction.commit();   
                } catch (Exception e) {   
                        userTransaction.rollback();   
                        e.printStackTrace();   
                }   
        }   
} 
 

jpa
JPQL就是一种查询语言,具有与 SQL 相类似的特征, JPQL 是完全面向对象的,具备继承、多态和关联等特性,和hibernate HQL很相似。 
 
查询语句的参数 
JPQL 语句支持两种方式的参数定义方式 : 命名参数和位置参数 。 。在同一个查询语句中只允许使用一种参数定义方式。 
 
命令参数的格式为:“ : + 参数名” 
例:
Query query = em.createQuery("select p from Person p where p.personid=:Id ");
query.setParameter("Id",new Integer(1)); 
 
位置参数的格式为“ ?+ 位置编号” 
例:
Query query = em.createQuery("select p from Person p where p.personid=?1 ");
query.setParameter(1,new Integer(1)); 
 
 
如果你需要传递 java.util.Date 或 java.util.Calendar 参数进一个参数查询 ,你需要使用一个特殊的 setParameter() 方法 ,相关的 setParameter 方法定义如下: 
 
public interface Query 
{ 
// 命名参数查询时使用,参数类型为 java.util.Date 
Query setParameter(String name, java.util.Date value, TemporalType temporalType); 
// 命名参数查询时使用,参数类型为 java.util.Calendar 
Query setParameter(String name, Calendar value, TemporalType temporalType); 
// 位置参数查询时使用,参数类型为 java.util.Date 
Query setParameter(int position, Date value, TemporalType temporalType); 
// 位置参数查询时使用,参数类型为 java.util.Calendar 
Query setParameter(int position, Calendar value, TemporalType temporalType); 
} 
 
因为一个 Date 或 Calendar 对象能够描述一个真实的日期、时间或时间戳 . 所以我们需要告诉 Query 对象怎么使用这些参数,我们把 javax.persistence.TemporalType 作为参数传递进 setParameter 方法,告诉查询接口在转换 java.util.Date 或 java.util.Calendar 参数到本地 SQL 时使用什么数据库类型 。 
 
下面通过实例来学习JPQL语句,例子的entity Bean 有 Person, Order, OrderItem , 他们之间的关系是:一个 Person 有多个 Order, 一个 Order 有多个 OrderItem 。 
 
JPQL语句的大小写敏感性:除了 Java 类和属性名称外,查询都是大小写不敏感的 。所以, SeLeCT 和 sELEct 以及 SELECT 相同的,但是 com.foshanshop.ejb3.bean.Person 和 com.foshanshop.ejb3.bean.PERSon 是不同的, person.name 和 person.NAME 也是不同的。 
 
 
命名查询 
可以在实体 bean 上通过 @NamedQuery or @NamedQueries 预先定义一个或多个查询语句,减少每次因书写错误而引起的 BUG 。通常把经常使用的查询语句定义成命名查询 。 
 
定义单个命名查询:
@NamedQuery (name= "getPerson" , query= "FROM Person WHERE personid=?1" ) 
@Entity 
public class Person implements Serializable{ 
 
如果要定义多个命名查询,应在 @javax.persistence.NamedQueries里定义 @NamedQuery : 
@NamedQueries ({ 
@NamedQuery (name= "getPerson" , query= "FROM Person WHERE personid=?1" ), 
@NamedQuery (name= "getPersonList" , query= "FROM Person WHERE age>?1" ) 
}) 
@Entity 
public class Person implements Serializable{ 
 
当命名查询定义好了之后,我们就可以通过名称执行其查询。代码如下: 
Query query = em . createNamedQuery("getPerson") ; 
query.setParameter(1, 1); 
 
 
排序 (order by) 
"ASC" 和 "DESC" 分别为升序和降序, JPQL 中默认为 asc 升序 
例: 
// 先按年龄降序排序,然后按出生日期升序排序 
Query query = em.createQuery("select p from Person p order by p.age desc, p.birthday asc "); 
 
查询部分属性 
通常来说,都是针对 Entity 类的查询,返回的也是被查询的 Entity 类的实体。J P QL 也允许我们直接查询返回我们需要的属性,而不是返回整个 Entity 。在一些 Entity 中属性特别多的情况,这样的查询可以提高性能 
例:
// 只 查询我们感兴趣的属性 ( 列 ) 
Query query=em.createQuery("select p.personid, p.name from Person p order by p.personid desc "); 
// 集合中的元素不再是 Person, 而是一个 Object[] 对象数组 
List result = query.getResultList(); 
if (result!=null){ 
Iterator iterator = result.iterator(); 
while( iterator.hasNext() ){ 
Object[] row = ( Object[]) iterator.next(); 
int personid = Integer.parseInt(row[0].toString()); 
String PersonName = row[1].toString(); 
。。。。 
} 
} 
 
查询中使用构造器 (Constructor) 
JPQL 支持将查询的属性结果直接作为一个 java class 的构造器参数,并产生实体作为结果返回 。例如上面的例子只获取 person entity bean的name and personid属性,我们不希望返回的集合的元素是object[],而希望用一个类来包装它。就要用到使用构造器 。 
例: 
public class SimplePerson { 
 private Integer personid; 
private String name ; 
   。。。。 
public SimplePerson() { 
} 
public SimplePerson(Integer personid, String name) { 
this . name = name; 
this . personid = personid; 
} 
} 
 
查询代码为:
// 我们把需要的两个属性作为 SimplePerson 的构造器参数,并使用 new 函数。 
Query query = em.createQuery(" select new com.foshanshop.ejb3.bean.SimplePerson(p. personid, p.name) from Person p order by p.personid desc "); 
// 集合中的元素是 SimplePerson 对象 
List result = query.getResultList(); 
if (result!=null){ 
Iterator iterator = result.iterator(); 
while( iterator.hasNext() ){ 
SimplePerson simpleperson = (SimplePerson) iterator.next(); 
。。。。 
} 
} 
 
聚合查询 (Aggregation) 
JPQL 支持的聚合函数 包括: 
1. AVG() 
2. SUM() 
3. COUNT() ,返回类型为 Long ,注意 count(*) 语法在 hibernate 中可用,但在 toplink 其它产品中并不可用 
4. MAX() 
5. MIN() 
 
例:
// 获取最大年龄 
Query query = em.createQuery("select max(p.age) from Person p"); 
Object result = query.getSingleResult(); 
String maxAge = result.toString(); 
// 获取平均年龄 
query = em.createQuery("select avg(p.age) from Person p"); 
// 获取最小年龄 
query = em.createQuery("select min(p.age) from Person p"); 
// 获取总人数 
query = em.createQuery("select count(p) from Person p"); 
// 获取年龄总和 
query = em.createQuery("select sum(p.age) from Person p"); 
 
如果聚合函数不是 select...from 的唯一一个返回列,需要使用 "GROUP BY" 语句 。 "GROUP BY" 应该包含 select 语句中除了聚合函数外的所有属性。 
例:
// 返回男女生各自的总人数 
Query query = em.createQuery(" select p.sex, count(p) from Person p group by p.sex "); 
// 集合中的元素不再是 Person, 而是一个 Object[] 对象数组 
List result = query.getResultList(); 
 
如果还需要加上查询条件,需要使用 "HAVING" 条件语句而不是 "WHERE" 语句 
例:
// 返回人数超过 1 人的性别 
Query query = em.createQuery("select p.sex, count(p) from Person p group by p.sex having count(*) >?1"); 
// 设置查询中的参数 
query.setParameter(1, new Long(1)); 
// 集合中的元素不再是 Person, 而是一个 Object[] 对象数组 
List result = query.getResultList(); 
关联 (join) 
JPQL 仍然支持和 SQL 中类似的关联语法: 
left out join/left join 
inner join 
left join fetch/inner join fetch 
 
 
left out join/left join 等,都是允许符合条件的右边表达式中的 Entiies 为空( 需要显式使用 left join/left outer join 的情况会比较少。 ) 
例:
// 获取 26 岁人的订单 , 不管 Order 中是否有 OrderItem 
select o from Order o left join o.orderItems where o.ower.age=26 order by o.orderid 
 
 
inner join 要求右边的表达式必须返回 Entities 。 
例:
// 获取 26 岁人的订单 ,Order 中必须要有 OrderItem 
select o from Order o inner join o.orderItems where o.ower.age=26 order by o.orderid 
 
 
!!重要知识点 : 在默认的查询中, Entity 中的集合属性默认不会被关联,集合属性默认是延迟加载 ( lazy-load ) 。那么, left fetch/left out fetch/inner join fetch 提供了一种灵活的查询加载方式来提高查询的性能。 
例:
private String QueryInnerJoinLazyLoad(){ 
// 默认不关联集合属性变量 (orderItems) 对应的表 
Query query = em.createQuery("select o from Order o inner join o.orderItems where o.ower.age=26 order by o.orderid"); 
List result = query.getResultList(); 
if (result!=null && result.size()>0){ 
// 这时获得 Order 实体中 orderItems( 集合属性变量 ) 为空 
Order order = (Order) result.get(0); 
// 当需要时, EJB3 Runtime 才会执行一条 SQL 语句来加载属于当前 Order 的 
//OrderItems 
Set<OrderItem> list = order.getOrderItems (); 
Iterator<OrderItem> iterator = list.iterator(); 
if (iterator.hasNext()){ 
OrderItem orderItem =iterator.next(); 
System.out.println (" 订购产品名: "+ orderItem.getProductname()); 
} 
} 
 
上面代码在执行 "select o from Order o inner join o.orderItems where o.ower.age=26 order by o.orderid" 时编译成的 SQL 如下( 他不包含集合属性变量 (orderItems) 对应表的字段 ) : 
select order0_.orderid as orderid6_, order0_.amount as amount6_, order0_.person_id as 
person4_6_, order0_.createdate as createdate6_ from Orders order0_ inner join OrderItems 
orderitems1_ on order0_.orderid=orderitems1_.order_id, Person person2_ where 
order0_.person_id=person2_.personid and person2_.age=26 order by order0_.orderid 
 
 
上面代码当执行到 Set<OrderItem> list = order.getOrderItems(); 时才会执行一条 SQL 语句来加载 属于当前 Order 的 OrderItems ,编译成的 SQL 如下 : 
select orderitems0_.order_id as order4_1_, orderitems0_.id as id1_, orderitems0_.id as id7_0_, 
orderitems0_.order_id as order4_7_0_, orderitems0_.productname as productn2_7_0_, 
orderitems0_.price as price7_0_ from OrderItems orderitems0_ where orderitems0_.order_id=? 
order by orderitems0_.id ASC 
 
这样的查询性能上有不足的地方 。为了查询 N 个 Order ,我们需要一条 SQL 语句获得所有的 Order 的原始对象属性,但需要另外 N 条语句获得每个 Order 的 orderItems 集合属性。为了避免 N+1 的性能问题,我们可以利用 join fetch 一次过用一条 SQL 语句把 Order 的所有信息查询出来 
 
例子
// 获取 26 岁人的订单 ,Order 中必须要有 OrderItem 
Query query = em.createQuery("select o from Order o inner join fetch o.orderItems where 
o.ower.age=26 order by o.orderid"); 
 
上面这句HPQL编译成以下的 SQL : 
select order0_.orderid as orderid18_0_, orderitems1_.id as id19_1_, order0_.amount as 
amount18_0_,order0_.person_id as person4_18_0_, order0_.createdate as createdate18_0_, 
orderitems1_.order_id as order4_19_1_, orderitems1_.productname as productn2_19_1_, 
orderitems1_.price as price19_1_, orderitems1_.order_id as order4_0__, orderitems1_.id as id0__ 
from Orders order0_ inner join OrderItems orderitems1_ on 
order0_.orderid=orderitems1_.order_id, Person person2_ where 
order0_.person_id=person2_.personid and person2_.age=26 order by order0_.orderid, 
orderitems1_.id ASC 
 
上面由于使用了 fetch, 这个查询只会产生一条 SQL 语句,比原来需要 N+1 条 SQL 语句在性能上有了极大的提升 
 
 
排除相同的记录 DISTINCT 
使用关联查询,我们很经常得到重复的对象,如下面语句: 
" select o from Order o inner join fetch o.orderItems order by o.orderid " 
当有 N 个 orderItem 时就会产生 N 个 Order, 而有些 Order 对象往往是相同的,这时我们需要使用 DISTINCT 关键字来排除掉相同的对象 。 
例:
select DISTINCT o from Order o inner join fetch o.orderItems order by o.orderid 
 
 
 
比较 Entity 
在查询中使用参数查询时 ,参数类型除了 String, 原始数据类型 ( int, double 等 ) 和它们的对象类型 ( Integer, Double 等 ), 也可以是 Entity 的实例 。 
例:
// 查询某人的所有订单 
Query query = em.createQuery("select o from Order o where o.ower =?1 order by o.orderid"); 
Person person = new Person(); 
person.setPersonid(new Integer(1)); 
// 设置查询中的参数 
query.setParameter(1,person); 
 
 
批量更新 (Batch Update) 
HPQL支持批量更新 
例:
// 把所有订单的金额加 10 
Query query = em.createQuery(" update Order as o set o.amount=o.amount+10 "); 
//update 的记录数 
int result = query. executeUpdate (); 
 
 
批量删除 (Batch Remove) 
例:
// 把金额小于100的订单删除,先删除订单子项, 再删除订单 
Query query = em .createQuery( "delete from OrderItem item where item.order in(from Order as o where o.amount<100)" ); 
query. executeUpdate(); 
query = em .createQuery( "delete from Order as o where o.amount<100" ); 
query.executeUpdate(); //delete 的记录数 
 
 
使用操作符 NOT 
// 查询除了指定人之外的所有订单 
Query query = em.createQuery("select o from Order o where not(o.ower =?1) order by o.orderid"); 
Person person = new Person(); 
person.setPersonid(new Integer(2)); 
// 设置查询中的参数 
query.setParameter(1,person); 
 
 
使用操作符 BETWEEN 
select o from Order as o where o.amount between 300 and 1000 
 
使用操作符 IN 
// 查找年龄为 26,21 的 Person 
select p from Person as p where p.age in(26,21) 
 
使用操作符 LIKE 
// 查找以字符串 "li" 开头的 Person 
select p from Person as p where p.name like 'li%' 
 
使用操作符 IS NULL 
// 查询含有购买者的所有 Order 
select o from Order as o where o.ower is [not] null 
 
使用操作符 IS EMPTY 
IS EMPTY 是针对集合属性 (Collection) 的操作符 。可以和 NOT 一起使用 。 注:低版权的 Mysql 不支持 IS EMPTY 
// 查询含有订单项的所有 Order 
select o from Order as o where o.orderItems is [not] empty 
 
使用操作符 EXISTS 
[NOT]EXISTS 需要和子查询配合使用 。注:低版权的 Mysql 不支持 EXISTS 
// 如果存在订单号为 1 的订单,就获取所有 OrderItem 
select oi from OrderItem as oi where exists (select o from Order o where o.orderid=1) 
// 如果不存在订单号为 10 的订单,就获取 id 为 1 的 OrderItem 
select oi from OrderItem as oi where oi.id=1 and not exists (select o from Order o where o.orderid=10) 
 
 
字符串函数 
JPQL 定义了内置函数方便使用。这些函数的使用方法和 SQL 中相应的函数方法类似。包括: 
1. CONCAT 字符串拼接 
2. SUBSTRING 字符串截取 
3. TRIM 去掉空格 
4. LOWER 转换成小写 
5. UPPER 装换成大写 
6. LENGTH 字符串长度 
7. LOCATE 字符串定位 
 
例:
// 查询所有人员,并在姓名后面加上字符串 "_foshan" 
select p.personid, concat(p.name, '_foshan') from Person as p 
// 查询所有人员 , 只取姓名的前三个字符 
select p.personid, substring (p.name,1,3) from Person as p 
 
计算函数 
HPQL 定义的计算函数包括: 
ABS 绝对值 
SQRT 平方根 
MOD 取余数 
SIZE 取集合的数量 
 
例:
// 查询所有 Order 的订单号及其订单项的数量 
select o.orderid, size (o.orderItems) from Order as o group by o.orderid 
// 查询所有 Order 的订单号及其总金额 /10 的余数 
select o.orderid, mod (o.amount, 10) from Order as o 
 
 
子查询 
子查询可以用于 WHERE 和 HAVING 条件语句中 
例:
// 查询年龄为 26 岁的购买者的所有 Order 
select o from Order as o where o.ower in(select p from Person as p where p.age =26) 
 
 
结果集分页 
有些时候当执行一个查询会返回成千上万条记录,事实上我们只需要显示一部分数据。这时我们需要对结果集进行分页 , QueryAPI 有两个接口方法可以解决这个问题: setMaxResults( ) 和 setFirstResult( ) 。 
 
setMaxResults 方法设置获取多少条记录 
setFirstResult 方法设置从结果集中的那个索引开始获取 (假如返回的记录有 3 条,容器会自动为记录编上索引,索引从 0 开始,依次为 0 , 1 , 2 ) 
 
例: 
public List getPersonList( int max, int whichpage) { 
try { 
int index = (whichpage-1) * max; 
Query query = em .createQuery( "from Person p order by personid asc" ); 
List list = query. setMaxResults(max) . 
setFirstResult(index) . 
getResultList(); 
em .clear(); // 分离内存中受EntityManager管理的实体bean,让VM进行垃圾回收 
return list; 
} 
Global site tag (gtag.js) - Google Analytics