`
xinklabi
  • 浏览: 1560799 次
  • 性别: Icon_minigender_1
  • 来自: 吉林
文章分类
社区版块
存档分类
最新评论

hibernate hbm转换Java|级联等概念介绍|级联删除

 
阅读更多

4.4.3. 根据映射对象生成映射文件上节提到了用hbm2java将影射文件生成映射对象,依靠xdoclet标签完成。xdoclet也是依靠此标签完成与影射文件的 同步。这样实际开发中会带来很大的便利,我们只要维护代码,而不需要手动维护与影射文件的同步。xdoclet标签 可以由上节讲的方法去转化得来,当然如果熟悉了xdoclet标签,手动完成即可。xdoclet的使用很方便,可以加入我 们已有的ant任务中(如果尚未了解Ant,请参看相关章节)。
1、下载xdoclet。
2、建立构建文件
例 4.12. build.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="XDoclet Examples" default="hibernate" basedir=".">
<property name="xdoclet.root.dir" value="c:/xdoclet-1.2.2"/>
<property name="xdoclet.lib.dir" value="${xdoclet.root.dir}/lib"/>
<property name="samples.gen-src.dir" value="./gen-src"/>
<path id="classpath">
<fileset dir="${xdoclet.lib.dir}">
<include name="*.jar"/>
</fileset>
</path>
<taskdef
name="hibernatedoclet"
classname="xdoclet.modules.hibernate.HibernateDocletTask"
classpathref="classpath"
/>
<target name="hibernate" description="Generate mapping documents">
<echo>+---------------------------------------------------+</echo>
<echo>| |</echo>
<echo>| R U N N I N G H I B E R N A T E D O C L E T |</echo>
<echo>| |</echo>
<echo>+---------------------------------------------------+</echo>
<hibernatedoclet
destdir="${samples.gen-src.dir}"
excludedtags="@version,@author,@todo,@see"
addedtags="@xdoclet-generated at ${TODAY},@copyright The XDoclet Team,@author XDoclet,@version ${version}"
force="false"
verbose="true">
<fileset dir="${samples.gen-src.dir}">
<include name="com/m5/Book.java"/>
</fileset>
<hibernate version="2.1"/>
</hibernatedoclet>
</target>
</project>
build.xml中的目录结构均为笔者环境的,使用时请修改成对应的目录。
3、运行ant,在输出目录生成对应的影射文件。
建议:如果你觉得hibernate的映射文件放在一个xml文件更为方便,可以通过修改xdoclet的源码, 使其生成的映射文件全部放置在制定的xml文件中,这样生成新的映射文件时不需要去维护hibernate 的配置文件中对影射文件的引用,当然也有弊端,多人开发时,关于版本控制冲突,以及可读性降低。 以上建议仅供参考。
4.5. 专用词
在讲述关联关系之前,有必要解释下一些专用词的含义
4.5.1. cascade(级联)级联在编程中经常接触,写过触发器来修改或删除关联表相记录的一定会知道,触发器的作用是当 主控表信息改变时,用来保证其关联表中数据同步更新。比如一个employee存放职员信息,一个 timecard存放职员的考勤信息,当从职员表中删除一个职员时,timecard表中对应的考勤信息 已经没有意义,因为其所属的职员已不存在,如果继续留在timecard表中就成了没用的也称脏数据。 理想的做法是在删除职员信息的同时将该职员信息对应的考勤信息也删除。在hibernate中如果要达到这个 效果只需要设置cascade属性值即可。当然是否进行级联关系要根据实际情况慎重考虑。
4.5.2. inverse(反转)表与表之间的关联,我们通常将主动发起关联请求的表称为主动表,被关联的表成为被动表,hibernate中 将此概念冠以在表所对应的对象上,因此将主动发起关联请求的对象称为主动对象或主控对象,被关联的对象 称为被动对象或被控对象。hibernate由主动对象维护关联关系,在实际中经常碰到一个对象的关联角色并不 那么明确,如双向关联,这时inverse值用来标明由谁来维护关联关系。设为true时反转控制角色,即由该 属性关联的对象维护关联关系。
4.5.3. Lazy Loading(延时装载)延时装载主要是从性能方面的考虑,对于 “select coulmn1 from table”和“select * from table”语句 的性能比较,相信大家不会有异议,第一条的执行性能要高于第二条,当然这个表中字段存储的信息应该能充分 体现出优越性为前提,比如说一个employee表中存放有,职员姓名、年龄、照片等,如果只需要查看姓名和年龄, 那么照片信息就不应该附带出来。表与表之间的关联也应如此,如果不需要用到关联表中的数据就不应该去进行关 联操作,或在需要的时候才启动关联操作。让数据在最恰当的时候才出现,这就是延时装载。
4.6. 一对一表关联操作
前面章节的例子是单表的操作,实际开发中表之间的关联操作是必不可少的。 本章以书籍与出版社之间的关联为例,来讲述一对一的关联操作。
一对一关系在hibernate中以one-to-one表示,本例中以Book类为主动连接方,因此在Book.java中加入 关联Publish的属性。一对一关联在hibernate中有两种方式:
主键关联:不需借助外部字段,直接通过两个表的主键进行关联,因此必须保证两个表的主键值一 致,这通常通常借助foreign标识符生成器策略来完成。简单来说,这种情况就是两个表的主键 相等的内连接。
唯一外键关联:在主动方加入外键进行关联,这样主动方与被动方的影射关系实际上就成了多对一的关联。
为方便查询,在此描述one-to-one节点的属性含义(也可参考hibernate的官方指导手册,有中英文对照很方便)
<one-to-one
name="propertyName" (1)
class="ClassName" (2)
cascade="all|none|save-update|delete" (3)
constrained="true|false" (4)
outer-join="true|false|auto" (5)
property-ref="propertyNameFromAssociatedClass" (6)
access="field|property|ClassName" (7)
/>
(1) name:映射属性的名称。

(2) class(可选):被关联的类的名称,如果省略此属性,则通过反射机制得到与此属性名称一致的类。

(3) cascade(可选):表明操作是否从父对象级联到被关联的对象,all,为所有变更动作都进行级联操作;none,为 从来不作级联操作;save-update,为insert,update动作时作级联操作。delete,为delete动作时作级联操作。

(4) constrained(可选):表明该类对应的表对应的数据库表,和被关联的对象所对应的数据库表之间,通过一个外键 引用对主键进行约束。这个选项影响save()和delete()在级联执行时的先后顺序。

(5) outer-join(可选):是否允许外连接抓取;默认是auto,关联对象没有采用proxy机制时使用外联接。

(6) property-ref(可选):指定关联类的一个属性,这个属性将会和本外键相对应。默认为关联类的主键。

(7) access(可选):Hibernate用来访问属性的策略,默认是property.


首先来看通过主键进行一对一的关联操作:
表 4.2. book
id name price
1 《Basic》 12.00
2 《Pasic》 15.00

表 4.3. Publish
id name address
1 机械出版社 北京朝阳区
2 教育出版社 北京海底区

1、建立映射文件
例 4.13. hibernate_map.xml
<hibernate-mapping>
<!--one to one-->
<class name="hibernate.relation.oneToOne.Book" table="Book">
<id name="id" column="id" type="java.lang.Integer">
<generator class="foreign">
<param name="property">publish</param>
</generator>
</id>
<property name="name" type="java.lang.String" column="name" length="100" not-null="true" />
<property name="price" type="long" column="price" length="100" not-null="true" />
<one-to-one name="publish" class="hibernate.relation.oneToOne.Publish" cascade="none" outer-join="auto" constrained="false" />
</class>

<class name="hibernate.relation.oneToOne.Publish" table="Publish">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native"></generator>
</id>
<property name="name" type="java.lang.String" column="name" length="100" not-null="true" />
<property name="address" type="java.lang.String" column="address" length="100" not-null="true" />
</class>
</hibernate-mapping>
2、建立映射类
例 4.14. Book.java
package hibernate.relation.oneToOne;
import java.io.Serializable;
import org.apache.commons.lang.builder.ToStringBuilder;
public class Book implements Serializable {
private Integer id;
private String name;
private long price;
private Publish publish = null;
public Book(Integer id, String name, long price) {
this.id = id;
this.name = name;
this.price = price;
}
public Book() {
}
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}

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

public long getPrice() {
return this.price;
}
public void setPrice(long price) {
this.price = price;
}


public Publish getPublish()
{
return this.publish;
}

public void setPublish(Publish publish)
{
this.publish = publish;
}
public String toString() {
return new ToStringBuilder(this)
.append("id", getId())
.toString();
}
}
例 4.15. Publish.java
package hibernate.relation.oneToOne;
import java.io.Serializable;
import org.apache.commons.lang.builder.ToStringBuilder;
public class Publish implements Serializable {
private Integer id;
private String name;
private String address;
public Publish(Integer id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public Publish() {
}
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return this.address;
}
public void setAddress(String address) {
this.address = address;
}
public String toString() {
return new ToStringBuilder(this)
.append("id", getId())
.toString();
}
}
3、演示代码
例 4.16. BookTest.java
public class BookTest extends TestCase
{
private String hql;
private SessionFactory sessionFactory;
private Session session;
private List list;

protected void setUp() throws Exception
{
File file = new File("d:\\hibernate.cfg.xml");
Configuration config = new Configuration().configure(file);
sessionFactory = config.buildSessionFactory();
session = sessionFactory.openSession();
}

public void testSave() throws HibernateException
{
Book book = new Book();
book.setName("《Basic》");
book.setPrice(Long.parseLong("12"));
Publish publish = new Publish();
publish.setName("机械出版社");
publish.setAddress("北京朝阳");
book.setPublish(publish);
session.save(book);
session.flush();
}

public void tesQuery() throws HibernateException
{
String publishName = null;
hql = "from hibernate.relation.oneToOne.Book as book where book.id = 1";
List books = session.find(hql);
for ( int i=0; i < books.size(); i++ )
{
Book book = (Book)books.get(i);
publishName = book.getPublish().getName();
System.out.println("对应的出版社为:" + publishName);
}
}
}
插入时将执行如下语句:
Hibernate: insert into Publish (name, address) values (?, ?)
Hibernate: insert into Book (name, price, id) values (?, ?, ?)
这是因为我们在Book映射文件中设置了其id值为外键生成策略,所以Hibernate会先插入 Publish,然后用publish的主键值填充Book的主键值,以保证Publish与Book表的主键 值相等。
执行查询语句将执行如下语句:
Hibernate: select book0_.id as id, book0_.name as name, book0_.price as price from Book book0_ where (book0_.id=39 )
Hibernate: select publish0_.id as id0_, publish0_.name as name0_, publish0_.address as address0_ from Publish publish0_ where publish0_.id=?
以上示例通过Book、publish两个表的主键形成关联,接下来看如何通过外键关联完成该例子。
4.7. 多对一表关联操作
我们在Book表中添加publishId的外键,用来与publish表形成关联。
表 4.4. book
id publishId name price
1 1 《Basic》 12.00
2 2 《Pasic》 15.00

1、建立映射文件
例 4.17. hibernate_map.xml
<hibernate-mapping>
<!--one to one-->
<class name="hibernate.relation.oneToOne.Book" table="Book">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native" />
</id>
<property name="name" type="java.lang.String" column="name" length="100" not-null="true" />
<property name="price" type="long" column="price" length="100" not-null="true" />
<many-to-one name="publish" class="hibernate.relation.oneToOne.Publish" cascade="all" outer-join="auto"/>
</class>
...
</hibernate-mapping>
只将book映射文件的one-to-one修改成many-to-one,其他的保持不变,执行BookTest.java文件,将看到 与one-to-one同样的效果。(注意:cascade="all",这里设置级联是必须的,因为在插入book时应该先得到 publishid的值。)
上面的例子都是以Book为主动关联方进行操作,如果需要在操作Publish时获取关联的Book对象,我们需要在 Publish中加入与Book的关联映射,这样Book与Publish之间就形成了双向关联,这里假设Publish与Book是 一对多的关系,具体操作请看下一章节。
4.8. 一对多表关联操作
1、在publish映射中加入一对多关系
例 4.18. hibernate_map.xml
...
<class name="hibernate.relation.oneToOne.Publish" table="Publish">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native"></generator>
</id>
<property name="name" type="java.lang.String" column="name" length="100" not-null="true" />
<property name="address" type="java.lang.String" column="address" length="100" not-null="true" />
<set name="book" table="book" lazy="false" cascade="none" sort="unsorted" inverse="false">
<key column="publishId" />
<one-to-many class="hibernate.relation.manyToOne.Book" />
</set>
</class>
2、在Publish映射类中加入book属性
例 4.19. Publish.java
private Set book = new HashSet();
public Set getBook()
{
return book;
}
public void setBook(Set book)
{
this.book = book;
}
这样就能在操作Publish时也能获取到与之关联的Book信息,看测试代码:
例 4.20. Publish.java
Publish publish = (Publish)session.get(Publish.class,Integer.valueOf(1));
Set books = publish.getBook();
for (Iterator it = books.iterator(); it.hasNext();)
{
Book book = (Book)it.next();
System.out.println("对应的书籍为:" + book.getName());
}
执行上面的代码显示的结果为:
Hibernate: select publish0_.id as id0_, publish0_.name as name0_, publish0_.address as address0_ from Publish publish0_ where publish0_.id=?
Hibernate: select book0_.id as id__, book0_.publishId as publishId__, book0_.id as id0_, book0_.name as name0_, book0_.price as price0_, book0_.publishId as publishId0_ from Book book0_ where book0_.publishId=?
对应的书籍为:《Basic》
4.9. 多对多表关联操作
多对多的关联在实际的开发中也是经常被用到的,假设现有一个员工表来存放所有员工的信息, 一个福利项目表存放福利明细,要记录每个员工享有的福利明细,同一福利项,多个员工均可享有, 一个员工也可以享有多项福利,这就形成了多对多的关联,在这里我们加入一个福利明细表充当 关联其两者的中间表(多对多的关联一般都是通过中间表进行关联的)。看具体实现:
1、表结构如下:
表 4.5. Welfare(福利项目表)
id(主键递增) name money
1 饭补 250.00
2 交通补助 200.00
3 岗位补助 500.00

表 4.6. Empolyee(人员表)
id(主键递增) name job
1 王一 部门经理
2 李二 程序员

表 4.7. Empolyee_Welfare(员工福利明细表)
id(主键递增) EmpolyeeID(员工ID) WelfareID(福利项ID)
1 1(王一) 1(饭补)
2 1(王一) 2(交通补助)
3 1(王一) 3(岗位补助)
4 2(李二) 1(饭补)
5 2(李二) 2(交通补助)

2、编写影射文件
例 4.21. Hibernate_map.xml
<class name="hibernate.relation.manyToMany.Employee" table="Employee">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native"></generator>
</id>
<property name="name" type="java.lang.String" column="name" length="100" not-null="true" />
<property name="job" type="java.lang.String" column="job" length="100" not-null="true" />
<set name="welfare" table="Empolyee_Welfare" lazy="false">
<key column="EmployeeId" />
<many-to-many class="hibernate.relation.manyToMany.Welfare" column="WelfareId" />
</set>
</class>
<class name="hibernate.relation.manyToMany.Welfare" table="Welfare">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native"></generator>
</id>
<property name="name" type="java.lang.String" column="name" length="100" not-null="true" />
<property name="money" type="java.lang.Double" column="money" length="18" not-null="true" />
<set name="employee" table="Empolyee_Welfare" lazy="false" inverse="true">
<key column="WelfareId" />
<many-to-many class="hibernate.relation.manyToMany.Employee" column="EmployeeId" />
</set>
</class>
3、编写映射类
例 4.22. Employee.java
public class Employee
{
private int id;
private String name;
private String job;
private Set welfare = new HashSet();
...
public Set getWelfare()
{
return this.welfare;
}

public void setWelfare(Set welfare)
{
this.welfare = welfare;
}
}
例 4.23. Welfare.java
public class Welfare
{
private int id;
private String name;
private Double money;
private Set employee = new HashSet();

...
public Set getEmployee()
{
return this.employee;
}

public void setEmployee(Set employee)
{
this.employee = employee;
}
}
4、测试代码
例 4.24. Employee.java
public void testSave() throws Exception
{

try
{
tx = session.beginTransaction();
Employee employee = new Employee();
employee.setName("王一");
employee.setJob("程序员");

Welfare welfare = new Welfare();
welfare.setMoney(Double.valueOf("250"));
welfare.setName("饭补");

employee.getWelfare().add(welfare);
welfare.getEmployee().add(employee);

session.save(employee);
session.save(welfare);
tx.commit();
}
catch(Exception ex)
{
if ( tx !=null )
{
tx.rollback();
}
}
finally
{
session.close();
}
}
执行以上代码Empolyee、Welfare、Empolyee_Welfare表中将各插入一条数据:
Hibernate: insert into Employee (name, job) values (?, ?)
Hibernate: insert into Welfare (name, money) values (?, ?)
Hibernate: insert into salary (EmployeeId, WelfareId) values (?, ?)
4.10. 与spring的结合使用
上面的例子中我的测试代码都是通过如下代码来建立hibernate的Session。
File file = new File("d:\\hibernate.cfg.xml");
Configuration config = new Configuration().configure(file);
sessionFactory = config.buildSessionFactory();
session = sessionFactory.openSession();
这样做的目的是为了更直观的说明对hibernate的使用,本节将演示结合Spring的使用,使代码更为简洁。
首先,修改spring的配置文件,如下:
<beans default-lazy-init="false" default-dependency-check="none" default-autowire="no">
<description>
</description>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>org.gjt.mm.mysql.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=GBK</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value></value>
</property>
</bean>

<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="dataSource">
<ref local="dataSource"/>
</property>
<property name="mappingResources">
<list>
<value>Hibernate_Map.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.cglib.use_reflection_optimizer">true</prop>
<prop key="hibernate.query.substitutions">true 1, false 0</prop>
</props>
</property>
</bean>
<beans>
上面增加的“dataSource”和“sessionFactory”节点,可以完全替换掉hibernate.cfg.xml的配置信息, 并且在org.springframework.orm.hibernate.LocalSessionFactoryBean类中封装了对hibernate的 session调用。请看如下调用代码:
SessionFactory sessionFactory = (SessionFactory)InitSpring.getInstance("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, false);
session.save(...);
上面的代码通过spring配置文件,实例sessionFactory,然后通过SessionFactoryUtils的getSession方法得到session实例。 关于在程序启动时怎样初始化Spring,请参考???
我们还可以通过Spring来实现Hibernate DAO,得到更简洁的调用:
<bean id="DAOTarget" class="hibernate.dao.test.MyDAO"
singleton="true"
lazy-init="default"
dependency-check="default"
autowire="default"
>
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
例 4.25. MyDAO.java
public class MyDAO extends HibernateDaoSupport
{
getHibernateTemplate().save(...);
}

上面的代码如果看的不是很明白,没有关系,在DAO章节将详细讲述其实现,这里代码只是想说明, 借助spring能让hibernate的应用更加简洁。
4.11. Hibernate DAO
在开始本章之前,先介绍DAO模式的概念,DAO是Data Access Object的缩写,DAO模式思想是将业务逻辑代码与 数据库交互代码分离,降低两者耦合。通过DAO模式可以使结构变得更为清晰,代码更为简洁,本节示例将结合Spring, 演练Hibernate Dao所带来的优越性。
为什么借助Spring实现Hibernate DAO:
Spring帮我们封装了针对Hibernate DAO的常用操作。
将业务对象与DAO对象的依赖转移到Spring配置文件中。
借助Spring AOP功能实现DAO对象中数据库访问的统一事务管理。
在开始演练Spring DAO之前,请看一段不借助DAO模式的代码:
例 4.26. MyApp.java
public void AddUser()
{
SessionFactory sessionFactory = (SessionFactory)InitSpring.getInstance("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, false);
User user = new User();
user.setName("王一");
String hql = " from User as user where user.name = '"+ user.name +"' "
List list = session.find(hql);
//进行逻辑判断,如果已经存在相同的用户则不允许添加
if ( list.size() > 0 )
{
...
}
else
{
try
{
Transaction tx = session.beginTransaction();
session.save(user);
tx.commit();
}
catch(Exception ex)
{
if ( tx !=null )
{
tx.rollback();
...
}
}
finally
{
...
session.close();
}

}
}
以上的代码实现的功能是添加一个用户,且在添加用户之前判断数据库是否有同名用户,应该说逻辑不算复杂, 我们将这种业务逻辑与数据库访问代码写在一块有如下不足之处:
降低重用性:该例子中实现了查找用户和添加用户的代码,这种功能很肯能在项目的很多地方都需要用到,但是 我们又不能直接调用AddUser()中实现的类似代码,因为该方法与具体的业务逻辑邦定在一块。
有可能降低代码可读性:当业务逻辑变得复杂时,在该例中既要实现业务逻辑,又要实现数据库访问代码。
造成重复编码:从上面例子的代码中我们可以看到附加了业务逻辑以外的代码,如事务管理、异常捕获。 最理想的业务类完成的功能应该是只实现其自身的业务逻辑。
我们期望的代码是:
例 4.27. MyApp.java
public void AddUser()
{
User user = new User();
user.setName("王一");
if ( findUser(user).size() >0 )
{
...
}
else
{
saveUser(user);
}
}
下面借助Spring来实现Hibernate Dao,来看看是否能达到我们期望的效果:
1、将访问数据库的代码从业务类MyApp.java中分离出来,放在DAO对象UserDao.java中
例 4.28. UserDao.java
public class UserDao extends HibernateDaoSupport
{
public List findUser(User user)
{
String hql = " from User as user where user.name = '"+ user.name +"' ";
return getHibernateTemplate().find(hql);
}
public void saveUser(User user)
{
getHibernateTemplate().save(user);
}
}
2、将业务类MyApp.java与DAO对象关联(在MyApp.java类中增加DAO的属性,以便通过Spring 注入DAO实例)。
例 4.29. Spring_Config.xml
<bean id="userDao" class="hibernate.dao.test.UserDao"
singleton="true"
lazy-init="default"
dependency-check="default"
autowire="default"
>
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
<bean id="myApp" class="hibernate.dao.test.MyApp"
singleton="true"
lazy-init="default"
dependency-check="default"
autowire="default">
<property name="dao">
<ref local="userDao" />
</property>
</bean>
3、业务类MyApp.java的实现
例 4.30. MyApp.java
public class MyApp
{
private UserDao dao;
public void setDao(UserDao dao)
{
this.dao = dao;
}

public void AddUser()
{
User user = new User();
user.setName("王一");
if ( dao.findUser(user).size() >0 )
{
...
}
else
{
dao.saveUser(user);
}
}
}
从MyApp.java可以看到与我们期望的效果几乎一样,在实际开发中我们应该让业务类与DAO类分别针对其接口 做实现,这样在代码中可以只针对于接口做引用,从而降低调用类与具体实现类的耦合,这里为更简洁的说明问 题省略对其各自接口的定义。
第 5 章 log4j
5.1. 概述
log4j是用于java语言的日志记录工具,一个完整的商业软件,日志是必不可少的。现实开发 中日志记录多种多样,有打印在控制台中,有记录成文本文件,有保存到数据库中等。日志信息也许需要 分为调试日志,运行日志,异常日志等。这些虽然实现简单,但是也繁琐。本章将介绍用log4j来实现日志 记录的种种情况。
5.2. 快速入门
1、下载log4j,http://logging.apache.org/log4j,将log4j.jar拷贝到项目的lib中, 并引用。
2、建立log4j的配置文件,本文中命名为log4j.xml,内容如下:
例 5.1. log4j.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> (1)
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="file" class="org.apache.log4j.FileAppender"> (2)
<param name="File" value="D:/mytest.log" /> (3)
<layout class="org.apache.log4j.PatternLayout"> (4)
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c:(%F:%L) %n - %m%n"/>
</layout>
</appender>
<root>
<priority value ="INFO"/> (5)
<appender-ref ref="file" /> (6)
</root>
</log4j:configuration>
(1) 申明验证该文档的dtd文件,”SYSTEM“说明是从本地寻找,因此需将log4j.dtd文件放入申明的路径中

(2) 该节点配置成日志以文件形式输出(org.apache.log4j.FileAppender)。log4j还提供打印日志到控制台 (org.apache.log4j.ConsoleAppender),以信息流格式传送到任何地方(org.apache.log4j.WriterAppender)。

(3) 指定日志文件的路径和名称。

(4) 指定记录日志的布局格式。log4j提供以html格式的布局(org.apache.log4j.HTMLLayout),自定义布局格式 (org.apache.log4j.PatternLayout),包含一些简单的日志信息,级别和信息字符串(org.apache.log4j.SimpleLayout) 包含详细的日志信息,如时间、线程、类别等信息。

(5) 设置日志输出的级别。log4j的日志常用级别如下,按优先级别从高到低分为:
Fatal:显示致命错误。
Error:显示错误信息。
Warn:显示警告信息。
Info:显示程序运行日志。
debug:显示调试信息。
只有日志级别大于或等于被设置级别,相应的日志才被记录。如本配置文件配置级别为Info,程序中除了debug级别的日志, 其它级别的日志都会被输出。

(6) 引用输出日志的方式。


3、演示使用log4j记录日志
例 5.2. MyLog.java
package com.m5;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
public class MyLog {
public static void main(String[] args)
{
String log4j = "d:\\log4j.xml" ;
DOMConfigurator.configure(log4j);
Logger logger = Logger.getLogger(MyLogServlet.class.getName());
try
{
int i = 10;
int n = 0 ;
int m = 10/0;
}
catch(Exception ex)
{
logger.info(ex.toString());
}
}
}
上面的程序会将捕捉到的异常信息写入D:/mytest.log文件。日志的书写格式输出目的地均可以通过log4j进行配置。 关于具体配置请参考log4j.xml及其说明,在此不再一一演示其效果。从上面的演示代码中可以看出对log4j的引用 也非常简洁,在实际运用中可以优化下导入log4j的配置文件部分。MyLog.java中的代码是为了最简捷清晰的说明对 log4j的使用。
4、实际环境中的应用(以web服务为tomcat的web项目为例)
在每个类中记录日志之前都敲一次装载log4j的配置文件的代码,显然是不合理的。通常我们在程序启动之前完成这些初始化工作。 spring一文中描述了对spring配置文件的初始化方法,同样,初始化log4j配置文件的装载也可以通过这个自定义的启动类完成。
例 5.3. web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
<display-name>Test</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring_bean.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
-->
<servlet>
<servlet-name>Init</servlet-name>
<servlet-class>com.m5.Base</servlet-class>
<init-param>
<param-name>log4jConfigLocation</param-name> (1)
<param-value>/WEB-INF/log4j.xml</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
</web-app>

(1) 配置log4j的配置文件路径。


例 5.4. Base.java
public class Base extends HttpServlet
{
WebApplicationContext wac = null;
public void init(ServletConfig config) throws ServletException
{
//ApplicationContext ac = new FileSystemXmlApplicationContext("bean.xml");
wac = WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext());
super.init(config);
InitSpring.Init((AbstractApplicationContext)wac);
String root = getServletConfig().getServletContext().getRealPath("/");
String log4j = getInitParameter("log4jConfigLocation"); (1)
DOMConfigurator.configure(root + log4j); (2)
}
}
(1) 得到配置文件路径。

(2) 装载配置文件,DOMConfigurator符合sigle模式,配置文件只需装载一次,全局即可调用。


通过以上配置,项目中可以通过如下引用:
例 5.5. MyLog.java
package com.m5;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
public class MyLog {
public static void main(String[] args)
{
//String log4j = "d:\\log4j.xml" ;
//DOMConfigurator.configure(log4j);
Logger logger = Logger.getLogger(MyLogServlet.class.getName());
try
{
int i = 10;
int n = 2 ;
int m = i/n;
}
catch(Exception ex)
{
logger.info(ex.toString());
}
}
}
规范合理的日志记录能让开发人员和维护人员事半功倍,在记录日志时还应该考虑不同的角色对日志内容可能会有 不同的需求。比如,软件正常情况下提供给用户的日志应该简洁明了,调试时提供给程序员的日志应该详细明确。 请看如下代码:
package com.m5;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
public class MyLog {
public static void main(String[] args)
{
String log4j = "d:\\log4j.xml" ;
DOMConfigurator.configure(log4j);
Logger logger = Logger.getLogger(MyLogServlet.class.getName());
try
{
int i = 10;
int n = 2 ;
int m = i/n;
logger.debug("以下信息为除法器运算跟踪:");
logger.warn("请注意被除数不能为0");
logger.info("除数为" + Integer.toString(i));
logger.info("被除数为" + Integer.toString(n));
logger.info("运算结果为:" + Integer.toString(m) );
}
catch(Exception ex)
{
logger.error(ex.toString());
}
}
}
调试的时候我们可以在log4j.xml配置文中指定级别为debug,输出所有日志。正式运行时将级别设定为error,只输出 错误日志。这样就不用每次在软件正式使用前注释或者删除调试的信息了。可以想象一下,如果要注释成百上千个段调试 代码,也是项繁琐的工作,再说在正式运行的时候如果出错,想看详细信息又得修改原代码,然后再编译。特别是异地非 远程控制的情况下如果要得到详细的调试日志那是件苦不堪言的事情,因为用户不会帮你去改代码。
log4j的配置文件还可以是属性文件,在此不再另述。
                                                                (转载

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics