Help

本文是上文的延续.

我想先介绍一下JBoss Developer Framework, 这是JBoss 社区推出的一个全新的项目, 旨在帮助开发者更好的理解和使用JBoss的相关技术, 它提供了大量的实例教程(50+), 视频, 文章的内容, 迁移向导(从Java EE 5到EE 6, 从Spring到Java EE)等, 教你一点点的学会Java EE 6相关的各种技术, 并且涵盖了 REST, HTML5 等新的热点技术.

本文即以kitchensink, 一个 JBoss Developer Framework 提供的实例为基础展开.

首先, 先让我介绍一下kitchensink吧, 最新的源代码可以在这里找到.

这个实例主要演示了如下几种技术:

  • Bean Validation 1.0
  • EJB 3.1
  • JAX-RS
  • JPA 2.0
  • JSF 2.0
  • CDI 1.0
  • Arquillian

想要运行这个实例的话, 你需要 JDK 6/7, Maven 3 和 JBoss AS 7

注意, 有一些依赖是只存在于JBoss 的maven 仓库中的, 所以, 可能需要对maven 的settings.xml文件做些配置, 添加JBoss maven仓库, 具体请参考这里这里

具体配置信息就不多说了, 上面给出的链接很详细, 这些也不是本文的重点, 接下来就让我们看看代码

首先是persistence.xml, 位于main/resources/META-INF/persistence.xml, 都是标准的位置

    <persistence version="2.0"
       xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
            http://java.sun.com/xml/ns/persistence
            http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
       <persistence-unit name="primary">
          <jta-data-source>java:jboss/datasources/KitchensinkQuickstartDS</jta-data-source>
          <properties>
             <property name="hibernate.hbm2ddl.auto" value="create-drop" />
             <property name="hibernate.show_sql" value="false" />
          </properties>
       </persistence-unit>
    </persistence>

可以看到, 这个文件定义的很简单, 就是定义了一个数据源和两个属性, 注意, hibernate.hbm2ddl.auto=creat-drop, 意思是在创建session factory的时候自动创建表结构, 关闭session factory的时候会自动drop掉表.

同时, 还可以看到main/resources目录中有import.sql这个文件, 当使用hibernate创建表结构的时候, 创建完成之后, hibernate会自动的导入import.sql文件, 这样可以添加一些初始数据.

另外, 引用的数据源是定义在main/webapp/WEB-INF/kitchensink-quickstart-ds.xml文件中的.

然后再来看看本文关注的另外一个方面, 实体定义:

    @Entity
    @XmlRootElement
    @Table(uniqueConstraints = @UniqueConstraint(columnNames = "email"))
    public class Member implements Serializable {
       /** Default value included to remove warning. Remove or modify at will. **/
       private static final long serialVersionUID = 1L;
    
       @Id
       @GeneratedValue
       private Long id;
    
       @NotNull
       @Size(min = 1, max = 25)
       @Pattern(regexp = "[A-Za-z ]*", message = "must contain only letters and spaces")
       private String name;
    
       @NotNull
       @NotEmpty
       @Email
       private String email;
    
       @NotNull
       @Size(min = 10, max = 12)
       @Digits(fraction = 0, integer = 12)
       @Column(name = "phone_number")
       private String phoneNumber;    
       // getters / setters
    } 

很简单的一个entity mapping, 需要注意的是javax.xml.bind.annotation.XmlRootElement 是JAXB里面的一个annotation, 在这里可以把这个实体对象转化成xml表示

还有就是这个entity里面定义了一些BV的annotation, 具体可以参考Hibernate Validator的文档.

Okay, 本实例中用到的其它技术暂时不做介绍了, 下面终于该进入正题了

上面介绍过了, 这个实例使用的是JBoss AS7中的数据源(java:jboss/datasources/KitchensinkQuickstartDS), 那么, 我们接下来就是看看如何做很少的更改, 让这个实例使用insinispan做为存储替换掉数据源中使用H2

首先, 是添加依赖, 因为我们这里是想要通过Hibernate OGM, 把实体对象保存进Infinispan当中, 所以只需要加入下面的依赖项即可:

        <dependency>
            <groupId>org.hibernate.ogm</groupId>
            <artifactId>hibernate-ogm-core</artifactId>
            <version>4.0.0-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>        
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.6</version>
        </dependency>
        <dependency>
            <artifactId>infinispan-core</artifactId>
            <groupId>org.infinispan</groupId>
            <version>5.1.5.FINAL</version>
            <scope>provided</scope>
        </dependency>

接下来就是修改persistence.xml了, 在上文曾经提到过, Hibernate OGM本身也是一个JPA的实现, 但是由于JBoss AS7默认集成的是Hibernate ORM 4, 所以我们需要在persistence.xml中显示的声明我们希望使用Hibernate OGM作为JPA的实现.

     <persistence version="2.0"
       xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
            http://java.sun.com/xml/ns/persistence
            http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
       <persistence-unit name="primary">
           <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
           <properties>
               <property name="hibernate.ogm.datastore.provider"
                         value="org.hibernate.ogm.datastore.infinispan.impl.InfinispanDatastoreProvider"/>
               <property name="hibernate.ogm.infinispan.configuration_resourcename" value="infinispan-ogm-config.xml"/>
           </properties>
           <class>org.jboss.as.quickstarts.kitchensink.model.Member</class>
       </persistence-unit>
    </persistence>

在这个更新过的文件中我们可以看到如下的变化:

  • 使用 org.hibernate.ogm.jpa.HibernateOgmPersistence 作为JPA的实现
  • 去掉了datasource的引用, Hibernate OGM不使用RMDBS
  • 通过 hibernate.ogm.datastore.provider指定使用infinispan作为data store
  • 通过 hibernate.ogm.infinispan.configuration_resourcename 属性指定infinispan的配置文件

src/main/webapp/WEB-INF/kitchensink-quickstart-ds.xml 这个文件已经没有用了,可以删掉.

至此, 配置方面就需要这么多的改动, 很简单吧

现在, 我们已经通过使用Hibernate OGM, 把底层的存储从RMDBS切换成了Infinispan, 但是, 由于RMDBS和NO-SQL 本质的不同, 我们还需要做一些修改.

使用uuid作为主键

在使用Hibernate ORM的时候, 我们通常会使用@GeneratedValue来得到数据库自动生成的id, 并且, 通常我们建议把id设置成long类型的以得到更好的性能.

可是, 如果使用的是NO-SQL的话, 如果是K-V类型的NO-SQL的话,他们是没有一个主键的概念的 (mongodb等文档型数据库是会提供自动生成的id的), 所以为了统一, 并且保证全局唯一, 在Hibernate OGM中我们建议使用UUID作为主键生成策略, 并且, 在Hibernate ORM中早已提供了此种策略, 我们在这里可以直接使用.

org.jboss.as.quickstarts.kitchensink.model.Member#id 需要修改成如下的样子.

   @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

注意, 改变了id的类型之后, 我们还需要修改 org.jboss.as.quickstarts.kitchensink.rest.MemberResourceRESTService#lookupMemberById

        @GET
    	@Path("/{id:[0-9][0-9,\\-]*}")
        @Produces(MediaType.APPLICATION_JSON)
        public Member lookupMemberById(@PathParam("id") String id) {
            Member member = repository.findById(id);
            if (member == null) {
                throw new WebApplicationException(Response.Status.NOT_FOUND);
            }
            return member;
        }

这个方法提供了一个通过REST接口来查找Member的功能, 但是因为我们已经把id的类型改成了String, 所以我们需要对@Path做一些修改, 让它能够接受字符 -- @Path("/{id:[0-9,a-z][0-9,a-z,\\-]*}")

查询

因为Hibernate OGM还是一个很年轻的项目, 有一些功能还没有完全的实现, 例如, 我们在JPA/Hibernate中经常使用的Criteria 查询, 但是幸好, Hibernate OGM和Hibernate Search有很好的集成, 我们可以使用Hibernate Search来完成这部分工作.

org.jboss.as.quickstarts.kitchensink.data.MemberRepository#findByEmail 方法是通过email来查询一个Member, 内部实现是通过Criteria来查询的.

org.jboss.as.quickstarts.kitchensink.data.MemberRepository#findAllOrderedByName 方法是查询所有的Member并且按照name排序, 同样是使用的Criteria.

这两个方法的实现很简单, 相信大家都能看懂.

那么, 我们现在就需要使用Hibernate Search替换掉这两个方法.

首先, 把Hibernate Search相关的依赖添加进pom当中

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-search-orm</artifactId>
            <version>4.2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-search-engine</artifactId>
            <version>4.2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-search-analyzers</artifactId>
            <version>4.2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-search-infinispan</artifactId>
            <version>4.2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-lucene-directory</artifactId>
            <version>5.1.5.FINAL</version>
        </dependency>

这里所依赖的org.hibernate:hibernate-search-infinispanorg.infinispan:infinispan-lucene-directory提供了一个Lucene的Directory的实现, 可以把Lucene的index保存在infinispan当中, 从而实现比保存在文件系统当中更好的性能和可扩展性(因为Infinispan是一个分布式的数据网格系统).

在persistence.xml当中,我们需要添加上两个Hibernate Search的属性 这里所依赖的org.hibernate:hibernate-search-infinispanorg.infinispan:infinispan-lucene-directory提供了一个Lucene的Directory的实现, 可以把Lucene的index保存在infinispan当中, 从而实现比保存在文件系统当中更好的性能和可扩展性(因为Infinispan是一个分布式的数据网格系统).

在persistence.xml当中,我们需要添加上两个Hibernate Search的属性

<property name="hibernate.search.default.directory_provider" value="infinispan"/>
    <property name="hibernate.search.infinispan.configuration_resourcename" value="infinispan.xml"/>
第一个hibernate.search.default.directory_provider告诉Hibernate Search使用Infinispan作为Lucene Index Directory, 第二个指定了Hibernate Search所使用的Infinispan的配置文件.

接下来, 我们需要对org.jboss.as.quickstarts.kitchensink.model.Member做一些改动, 添加上Hibernate Search所需要的Annotation.

   @Entity
    @XmlRootElement
    @Indexed
    @Table(uniqueConstraints = @UniqueConstraint(columnNames = "email"))
    @Proxy(lazy = false)
    public class Member implements Serializable {
       /** Default value included to remove warning. Remove or modify at will. **/
       private static final long serialVersionUID = 1L;
    
       @Id
       @GeneratedValue(generator = "uuid")
       @GenericGenerator(name = "uuid", strategy = "uuid2")
       private String id;
    
       @NotNull
       @Size(min = 1, max = 25)
       @Pattern(regexp = "[A-Za-z ]*", message = "must contain only letters and spaces")
       @Fields({
    		   @Field(analyze = Analyze.NO, norms = Norms.NO, store = Store.YES, name = "sortableStoredName"),
    		   @Field(analyze = Analyze.YES, norms = Norms.YES)
       })
       private String name;
    
       @NotNull
       @NotEmpty
       @Email
       @Field(analyze = Analyze.NO)
       private String email;
    
       @NotNull
       @Size(min = 10, max = 12)
       @Digits(fraction = 0, integer = 12)
       @Column(name = "phone_number")
       @Field(analyze = Analyze.NO)
       private String phoneNumber;

第一个是@Indexed, 它告诉Hibernate Search, 这个实体类是需要被索引的, 还有@Field (在name和phoneNumber属性上)告诉Hibernate Search这两个属性是需要被索引的 ,具体信息请参考Hibernate Search 文档

想要在保存一个实体对象的时候,让Hibernate Search自动索引的话, 我们需要使用org.hibernate.search.jpa.FullTextEntityManager来替换javax.persistence.EntityManager.

org.jboss.as.quickstarts.kitchensink.service.MemberRegistration#register这个方法调用了EntityManager#persist, 那么我们需要做的就是把这个类中的@Inject private EntityManager em;换成 @Inject private FullTextEntityManager em; 这里, 之前的EntityManager和现在的FullTextEntityManager都是由CDI负责自动注入的, 可是, CDI并不知道如何创建Hibernate Search所特有的FullTextEntityManager, 所以, 为了让自动注入工作, 我们需要一个Producer.

而这个Producer, 在我们的实例当中就是org.jboss.as.quickstarts.kitchensink.util.Resources, 它已经存在了, 用来提供EntityManager的注入和Logger的注入(org.jboss.as.quickstarts.kitchensink.util.Resources#produceLog)

我们只需要添加Hibernate Search的内容 (CDI相关内容可以参考Weld文档)

	@Produces
	public FullTextEntityManager getFullTextEntityManager() {
		return Search.getFullTextEntityManager( em );
	}

	@Produces
	@ApplicationScoped
	public SearchFactory getSearchFactory() {
		return getFullTextEntityManager().getSearchFactory();
	}

	@Produces
	@ApplicationScoped
	public QueryBuilder getMemberQueryBuilder() {
		return getSearchFactory().buildQueryBuilder().forEntity( Member.class ).get();
	}

Okay, 现在万事俱备, 我们可以使用Hibernate Search来替换org.jboss.as.quickstarts.kitchensink.data.MemberRepository中那两个使用了Criteria的方法了

注意, org.jboss.as.quickstarts.kitchensink.data.CriteriaMemberRepository#findById 是需要把参数的类型从long改成String的, 除此之外, Hibernate OGM是支持直接使用id进行查询的, 所以不需要修改.

org.jboss.as.quickstarts.kitchensink.data.MemberRepository#findByEmail 方法是通过email来查询一个Member, 内部实现是通过Criteria来查询的.

org.jboss.as.quickstarts.kitchensink.data.MemberRepository#findAllOrderedByName 方法是查询所有的Member并且按照name排序, 同样是使用的Criteria.

@Inject
	private QueryBuilder queryBuilder;

	@Inject
	private FullTextEntityManager em;

	@Override
	public Member findById(String id) {
		return em.find( Member.class, id );
	}

	@Override
	public Member findByEmail(String email) {
		Query luceneQuery = queryBuilder
				.keyword()
				.onField( "email" )
				.matching( email )
				.createQuery();
		List resultList = em.createFullTextQuery( luceneQuery )
				.initializeObjectsWith( ObjectLookupMethod.SKIP, DatabaseRetrievalMethod.FIND_BY_ID )
				.getResultList();
		if ( resultList.size() > 0 ) {
			return (Member) resultList.get( 0 );
		}
		else {
			return null;
		}
	}

	@Override
	public List<Member> findAllOrderedByName() {
		Query luceneQuery = queryBuilder
				.all()
				.createQuery();
		List resultList = em.createFullTextQuery( luceneQuery )
				.initializeObjectsWith( ObjectLookupMethod.SKIP, DatabaseRetrievalMethod.FIND_BY_ID )
				.setSort( new Sort( new SortField( "sortableStoredName", SortField.STRING_VAL ) ) )
				.getResultList();
		return resultList;
	}

可以看到, 在这个新的类中, 我们首先使用CDI自动注入了Hibernate Search的QueryBuilder和FullTextEntityManager (参见上面修改后的org.jboss.as.quickstarts.kitchensink.util.Resources 工厂类)

在findById方法中, FullTextEntityManager#find实际上是代理给Hibernate OGM来处理的.

而在其余两个方法中, 则完全是使用Hibernate Search的Query API创建了查询条件, 然后交过Lucene来搜索的, 还记得我们上面修改了Member类, 在它被保存的时候创建Lucene索引的吧 (另, 上面提到过, 这个索引也是保存在infinispan当中的)

部属到JBoss AS 7

方便的是, 我的同事Hardy已经准备好了这样一个修改过的项目, 你可以直接从前面的链接中下载到本文中所介绍到的项目的源代码.

这是一个maven项目, 所以你需要先安装好maven, Hardy还很贴心的在pom里面使用了cargo插件, 所以你不需要下载JBoss AS 7了(尽管我还是推荐你下载一个看看, 很值得的), cargo会自动帮你下载, 并且完成部属等事情.

  • 编译 $ mvn clean package
  • 运行 $ mvn cargo:run
  • 测试(基于Arquillian) $mvn test

跑起来之后你可以访问http://127.0.0.1:8080/ogm-kitchensink 来看看具体的效果.

另外, 如果你尝试输入一个不合法的名字, 电话号码或者email地址的话, 你会看到错误提示, 这就是Bean Validation所自动帮你提供的输入校验, 来看看代码, 还记得 org.jboss.as.quickstarts.kitchensink.model.Member 类中的属性上定义的Bean Validation Annotations么?(下面代码中我只保留了这部分的annotation)

        @NotNull
	@Size(min = 1, max = 50)
	@Pattern(regexp = "[A-Za-z ]*", message = "must contain only letters and spaces")
	private String name;
	
	@NotNull
	@NotEmpty
	@Email
	private String email;

        @NotNull
	@Size(min = 10, max = 12)
	@Digits(fraction = 0, integer = 12)
	private String phoneNumber;

Okay, 现在你已经有了一个使用Hibernate OGM + NO-SQL的程序跑在 JBoss AS7上面了

另外, JBoss AS 7的启动速度很快, 非常快!

23:27:10,050 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: 
JBoss AS 7.1.1.Final "Brontes" started in 2009ms - Started 133 of 208 services (74 services are passive or on-demand)

上面的日志是在我的机器上启动的速度, 应该和Tomcat也差不多了吧, 但是JBoss AS7 可是一个完整的通过Java EE6认证的应用服务器, 而Tomcat只是一个servlet container.

Hardy还非常贴心的提供了一个小工具来帮助我们自动的插入数据, 这就是在项目根目录下的member-generator.rb

要使用这个工具的话, 你首先需要安装(gem install)如下的gem:

  • gem install httparty
  • gem install nokogiri
  • gem install choice

然后就可以通过执行下面的命令来创建一些测试数据了.

ruby member-generator.rb -a http://localhost:8080/ogm-kitchensink -c 20

部属到 Openshift

openshift是Redhat所提供了一个Paas服务, 它背后的技术已经开源, 可以在这里找到其源代码和众多的实例项目.

openshift同时提供免费的服务和付费的服务, 对于我们简单的玩玩来讲, 免费的账户已经足够了, 但是如果你的项目是一个正式上线的项目, 还是推荐使用收费的带有支持的服务的.

openshift所支持的平台包括:

  • Java
  • Ruby
  • Node.js
  • Python
  • PHP
  • Perl

并且它还提供了开箱即用的数据库支持, 包括RMDBS和NO-SQL

  • Mysql
  • PostgreSQL
  • Mongodb

例如我们可以在常见部属一个自己的wordpress或者drupal程序, 很方便.

对于我们Java程序员来讲, openshift最重要的是它内置了JBoss AS7 (和其商业版本JBoss EAP6)的支持, 所以, 对于想要尝试Java EE6的童鞋来说就有福了, 你可以直接使用此服务.

首先, 第一步没得说, 先到这里来创建一个免费的账号.

然后从这里下载客户端, Linux / Mac / Windows都有对应的客户端供下载.

接着, 你需要创建一个domain, 命令为 rhc domain create -n ${my domain name}, 之后, 你所有部属到openshift上的应用都会是http://${app name}-${domain name}.rhcloud.com的格式(免费账户可以创建3个程序, 并且你可以使用自己的域名).

然后该创建应用了, 我们想要把这个程序部属到JBoss AS7上, 使用下面的命令rhc-create-app -a ${your app name} -t jbossas-7 --nogit, 命令完成之后, 你会从输出中看到有一个git repo的地址, 现在, 你可以把这个地址作为一个remote添加到之前clone出来的ogm-kitchensink项目当中

git remote add openshift ${repo-url}

然后, 把ogm-kitchensink 推送到openshift提供的git repo当中

git push -f openshift master

最后, 你就可以访问你的应用了, 地址如同之前所说的, 是

http://${your app name}-${your domain name}.rhcloud.com

我创建了一个demo, 可以访问http://ogm-stliu.rhcloud.com.

15 comments:
 
13. Jul 2012, 10:27 CET | Link
Bertrand Toussaint

Mr Liu,

Please write your articles in English.

I suppose you want also to share your knowledge with people who are not only chinese fluent !

Un peu de discipline, s'il vous plait !

谢谢 (xièxie) , Merci, Thank you :

ReplyQuote
 
13. Jul 2012, 11:42 CET | Link
domingo

ido que vols que te digui ... molt clar no ho acabo de veure jo, batualmon.

De totes maneres, crec que alguna de les bubotes que surten està malament.

 
13. Jul 2012, 13:14 CET | Link
Compared with the English, I prefer to read Chinese.
Redhat (JBoss) website, as if English is strong, do not like Chinese.
Otherwise, simply use Google translation click posted Forget!

The following is Chinese:
-------------------------
与英文相比,我更喜欢读中文。
在Redhat(以及JBoss)的网站上,好像英文强势,不喜欢中文。
要不,索性用谷歌翻译一下同时贴出来算了!
 
13. Jul 2012, 13:22 CET | Link
ratking
Un peu de discipline, s'il vous plait !
A little discipline, please!
一个小学科,拜托!

The following is Chinese:
-------------------------
discipline:训练、修养、磨练、自制、规律,纪律、风纪、秩序、惩罚,处罚、教规,戒律、学科、科目、训练,调教、惩罚,处罚、规定

The following is English:
-------------------------
discipline: training, cultivation, temper, self-restraint, law, discipline, discipline, order, punishment, punishment, religious rules, observance, discipline, subject, training, tuning, punishment, punishment, the provisions of
 
13. Jul 2012, 13:26 CET | Link
ratking

The following is English: ------------------------- HibernateOGM

 
13. Jul 2012, 16:53 CET | Link

Great that you like China. But this is an English Language site. If you want to deliver Chinese content, create a Chinese website.

 
14. Jul 2012, 06:19 CET | Link
ratking
Be tolerant to diversity, tolerance is a virtue.

The following is Chinese:
-------------------------
海纳百川,有容乃大。
 
17. Jul 2012, 17:02 CET | Link
Marc Schipperheyn wrote on Jul 13, 2012 10:53:
Great that you like China. But this is an English Language site. If you want to deliver Chinese content, create a Chinese website.

Well, I'm a Chinese, that's why I love China. and this is my blog, I can write blog in WHATEVER language I like I think.

 
17. Jul 2012, 17:07 CET | Link
Bertrand Toussaint wrote on Jul 13, 2012 04:27:
Mr Liu, Please write your articles in English. I suppose you want also to share your knowledge with people who are not only chinese fluent ! Un peu de discipline, s'il vous plait ! 谢谢 (xièxie) , Merci, Thank you :

yes, most readers of this site are English speakers I'd say, so, it is on my todo list to translate these two OGM posts to English.

thanks :D

 
17. Jul 2012, 17:09 CET | Link

no idea what this language it is, french?

 
17. Jul 2012, 19:55 CET | Link
Well, I'm a Chinese, that's why I love China. and this is my blog, I can write blog in WHATEVER language I like I think.

I stand corrected. I always thought this was the official blog for all things Hibernate. So, let me be more specific. It's a hindrance to read various languages in a single blog, especially if languages are included that look like hyroglyphics to the untrained eye, no offense. The suggestion/request still stands: create a separate blog if you want to discourse in a different language. Or perhaps this should be all chinese and start catering to that audience exclusively. Whichever is the case, mixing it up is not helpful.

 
18. Jul 2012, 00:27 CET | Link
Bertrand Toussaint | bertrand.toussaint66(AT)gmail.com
no idea what this language it is, french?

French it was !

Mr Liu, I wouldn't to offend you. I am French, I love China too (since at least twenty years, indeed, my wife is Chinese).

However, I have some difficulties with chinese reading. I would like to read and understand your articles, but I can't. I have not enough time to learn the 3.000 standard characters needed for that hard task.

I think, most of in.relation.to Every One... bloggers and readers have the same feeling.

So, it will be better for you to extend the scope of your articles from @Scope(Audience.CHINA) to @Scope(Audience.UNIVERSE)

 
27. Jul 2012, 15:28 CET | Link
Tio Malandra

Escribí en inglés, chino forro y la concha de tu madre

 
25. Aug 2012, 03:41 CET | Link
yongzhi.wu

真棒,看到中文的文章,还有在redhat.com工作的中国人!加油!

 
25. Sep 2012, 10:53 CET | Link
中文好

我觉得用中文写最好,更容易在国内推广hibernate技术

Post Comment