haojii

一个专注于技术的IT男

Follow me on TwitterRSS订阅

  • 首页
  • 关于
  • 玩具

Workshop – Walk in the Cloud with Google App Engine

五 31st

由Jacky发表在GoogleAppEngine

没有评论

I will hold a workshop of Google App Engine for the next codekata activity, welcome to join me.

Preview:

Click Here: http://jhaddressbook.appspot.com/contact

Slides:

GoogleAppEngine, Presentation, workshop

学车日志(5)

五 24th

由Jacky发表在曾经的一天

没有评论

今天运气很不错,小路考抽到起伏路,Happy,顺利通过。

学车

Cool demo with jQuery plugin FancyBox

五 21st

由Jacky发表在JavaScript

没有评论

猛击我查看效果

A Flash Demo

代码如下:

<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>

<script type="text/javascript" src="http://www.haojii.com/webdev/fancyboxdemo/fancybox/jquery.mousewheel-3.0.2.pack.js"></script>

<script type="text/javascript" src="http://www.haojii.com/webdev/fancyboxdemo/fancybox/jquery.fancybox-1.3.1.js"></script>

<link rel="stylesheet" type="text/css" href="http://www.haojii.com/webdev/fancyboxdemo/fancybox/jquery.fancybox-1.3.1.css" media="screen" />

<script type="text/javascript">
		$(document).ready(function() {
			$("#login").fancybox({
				'padding'			: 0,
				'autoScale'			: false,
				'transitionIn'		: 'none',
				'transitionOut'		: 'none'
			});
			$("#flash").fancybox({
				'padding'			: 0,
				'autoScale'			: false,
				'transitionIn'		: 'none',
				'transitionOut'		: 'none'
			});
		});
	</script>

<div>
<h1><a id="login" href="#register-form" title="This is the title for register form">猛击我吧!</a></h1>
    <div style="display:none"> 

<!-- Registration -->
		<div id="register-form" style="width: 500px; height: 300px; margin:auto;">
			<h1><a id="flash" href="http://www.adobe.com/jp/events/cs3_web_edition_tour/swfs/perform.swf">A Flash Demo</a></h1>
		</div>
   </div>
<!-- /Registration -->  

</div>
JavaScript, jQuery, web开发

美剧更新自动提醒服务(2)

五 20th

由Jacky发表在GoogleAppEngine

没有评论

美剧更新自动提醒服务已更新: http://jhnotifier.appspot.com/

源码host在: http://code.google.com/p/jhnotifier/

确切的说,这个应用目前只能叫做Easytv 更新通知服务,目前仅适合追美剧的有线通用户,当然追国产电视剧也是行的,程序的功能是美剧有更新时,主动发送邮件到你的邮箱实现通知。

现在支持多用户定制自己关心的电视剧,使用邮箱登陆上面的地址后,只要在Step2的表单里填入你关心的easytv剧集的URL地址即可体验,欢迎试用。

计划增加的功能:

1. 短信提醒

2. 支持任意用户,对关心的Easytv美剧提交自己的信息,收到更新通知 Done!

3. 增加其他的爬网页,发通知服务(例如美剧论坛BT种子更新,天气预报,或者是定制程度更高的应用)

google, GoogleAppEngine, Notifier

如何使用GoogleAppEngine的数据存储服务?

五 6th

由Jacky发表在GoogleAppEngine

没有评论

本文描述如果使用GoogleAppEngine的数据存储服务,即如何在GAE上创建,删除,查询数据。重点例举四种类型主键的使用。本文是GAE 文档Creating, Getting and Deleting Data的扩展展开。

代码示例基于 appengine-java-sdk-1.3.3.1 +GAE Eclipse插件

1. 数据存储基础

不管是使用传统的关系型数据库还是使用NOSQL架构的数据存储,我们的应用程序存储实体对象时一般都需要主键,方便于之后的数据查询和更新。

JavaEE架构中使用JPA规范统一POJO在关系型数据库中的映射与持久化。因为JPA主要是为关系型数据库设计的,所以GAE仅支持部分JPA规范的内容,GAE文档里关于JPA的说明也比较简陋。

GAE文档里主要介绍基于JDO的数据存储,JDO规范的好处是它抽象级别更高,并不关心底层的数据库是何种实现,所以对于Google的BigTable架构比JPA适用,当前GAE仅实现了JDO规范里面的部分内容(基于开源的DataNucleus)。

GAE文档里的最简单例子,使用JDO存储数据的4个步骤是

1.1 定义POJO数据类,添加JDO Annotation

Employee.java

import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @Persistent
    private String firstName;

    @Persistent
    private String lastName;

    @Persistent
    private Date hireDate;

    public Person(String firstName, String lastName, Date hireDate) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.hireDate = hireDate;
    }

    // Accessors for the fields.  JDO doesn't use these, but your application does.

    public Long getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    // ... other accessors...
}

 

@PersistenceCapable(identityType = IdentityType.APPLICATION)标识该POJO能够通过JDO存储到数据存储区。

@Persistent标识字段为需要持久存储的

@PrimaryKey标识字段为主键

@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)标识在已经被标识为主键的Long字段上标识该主键值自动填充

1.2 设置JDO

1.3 增强POJO使其与JDO实现相关联

这两步GAE Eclipse插件都帮我们搞定了

1.4 获取PersistenceManager实例操作数据类

PMF.java

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public final class PMF {
    private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");

    private PMF() {}

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}

 

1.4.1 存储数据对象

import java.util.Date;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

import Employee;
import PMF;

// ...
        Employee employee = new Employee("Alfred", "Smith", new Date());

        PersistenceManager pm = PMF.get().getPersistenceManager();

        try {
            pm.makePersistent(employee);
        } finally {
            pm.close();
        }

 

1.4.2 查询获得数据对象

JDO规范的查询格式和JPA的其实差不多


import java.util.List;
import Employee;

// ...
        String query = "select from " + Employee.class.getName() + " where lastName == 'Smith'";
        List<Employee> employees = (List<Employee>) pm.newQuery(query).execute();

 

2. 使用四种类型主键的实体创建,查询,删除

上文中已经领略最简单的主键的例子,在GAE中所有实体都有一个唯一的标识唯一性的键值。根据文档:一个完整的键包含若干条信息,其中包括应用程序ID、类型和实体ID。

直接读这个描述的话可能不太直观,这三个元素一起才能标识你需要在GAE上存储的实体,其实和JavaEE里配置DataSource+JPA ORM Annotation是类似的,可以这么比较

应用程序ID:相当于关系型数据库的Schema

类型:相当于关系型数据库的表

实体ID:相当于关系型数据库的主键

实体的主键字段可以有四种类型,长整型,字符串(未编码字符串),编码字符串形式的主键,com.google.appengine.api.datastore.Key 前面两个相对简单常见,比较容易用,后面两个稍微有点绕。

2.1 长整型

UserLong.java  POJO数据类


import java.util.Date;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class UserLong {

 @PrimaryKey
 @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
 private Long id;

 @Persistent
 private Date date = new Date();
 
 @Persistent
 private String name;
 
 @Persistent
 private String type;

 public UserLong() {

 }

 public UserLong(String name, String type) {
  super();
  this.name = name;
  this.type = type;
 }

// ...

单元测试类TestUserWithLongId,对使用长整型字段为主键的对象的创建,查询,删除

import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.appengine.tools.development.testing.LocalTaskQueueTestConfig;
import com.haojii.jdo.utils.PMF;

public class TestUserWithLongId {

 private static final LocalServiceTestHelper helper = new LocalServiceTestHelper(
   new LocalTaskQueueTestConfig(),
   new LocalDatastoreServiceTestConfig());

 @BeforeClass
 public static void setUpBeforeClass() throws Exception {
  helper.setUp();
 }

 @AfterClass
 public static void tearDownAfterClass() throws Exception {
  helper.tearDown();
 }
 

 @Test
 public void doTest() {
  
  PersistenceManager pm = PMF.get().getPersistenceManager();
  
  System.out.println("0 Create and persist UserLong with @PrimaryKey: Long id");
  UserLong userLong = new UserLong("name1",UserLong.class.getSimpleName());
  pm.makePersistent(userLong);
  
  System.out.println("1 Query UserLong with getObjectById");
     UserLong tmpUserLong = null;
     Long id = userLong.getId();
     tmpUserLong = pm.getObjectById(UserLong.class, id);
     System.out.println("userLong==tmpUserLong?"+(userLong==tmpUserLong));
     Assert.assertTrue(userLong == tmpUserLong);
    
     System.out.println("2 Query UserLong with name1");
     tmpUserLong = null;
     Query query = pm.newQuery(UserLong.class);
     query.setFilter("name == nameParam");
     query.setOrdering("date desc");
     query.declareParameters("String nameParam");
     try {
         List<UserLong> results = (List<UserLong>) query.execute("name1");
         System.out.println("All UserLong size:"+results.size());
         Assert.assertEquals(1, results.size());
        
         if (results.iterator().hasNext()) {
             for (UserLong e : results) {
              tmpUserLong = e;
                 System.out.println(" "+e);
             }
         } else {
          System.out.println("2 Query get empty result");
         }
     } finally {
         query.closeAll();
     }
    
     Assert.assertTrue(tmpUserLong!=null);
     if (tmpUserLong != null)
     {
      System.out.println("3 Update the searched result[0] UserLong with name1->NAME!");
      System.out.println("userLong==tmpUserLong?"+(userLong==tmpUserLong));
      Assert.assertTrue(userLong == tmpUserLong);
     
      tmpUserLong.setName("NAME!");
      pm.makePersistent(tmpUserLong);
     }
    
     System.out.println("4 Delete UserLong with name1");
     System.out.println(" ->"+userLong);
     pm.deletePersistent(userLong);

    
     query = pm.newQuery(UserLong.class);
     int size = ((List<UserLong>) query.execute()).size();
  System.out.println("All UserLong size:"+size);
     Assert.assertEquals(0, size);
 }

}

2.1.1: LocalServiceTestHelper是为GAE本地单元测试提供的辅助类,这里不再赘述,若不明白看一下前面的link的参考文档。

2.1.2: 创建持久化对象pm.makePersistent(entityObject);

2.1.3: 删除持久化对象pm.deletePersistent(entityObject);

2.1.4: 查询长整型主键的对象

方法一,pm.getObjectById(UserLong.class, id)

第一个参数是实体对象的类型,第二个参数是能标识实体的主键这里是长整型的id

方法二,使用JDO查询,可设置查询条件,Query query = pm.newQuery(UserLong.class);

 这个查询返回的是实体的列表集合

2.2 字符串

UserUnEncodedString.java


@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class UserUnEncodedString {
 @PrimaryKey
 private String name;
 @Persistent
 private String type;

 @Persistent
 private Date date = new Date();
 
 public UserUnEncodedString() {
  
 }
 public UserUnEncodedString(String name, String type) {
  super();
  this.name = name;
  this.type = type;
 }

TestUserWithUnEncodedStringId.java

	@Test
	public void doTest() {

		PersistenceManager pm = PMF.get().getPersistenceManager();

		System.out.println("0 Create and persist UserUnEncodedString with @PrimaryKey: String name");
		UserUnEncodedString userUnEncodedString = new UserUnEncodedString("key_name1",UserUnEncodedString.class.getSimpleName());
		pm.makePersistent(userUnEncodedString);

		System.out.println("1 Query UserUnEncodedString with getObjectById");
	    UserUnEncodedString tmpUserUnEncodedString = null;
	    String key = userUnEncodedString.getName();
	    tmpUserUnEncodedString = pm.getObjectById(UserUnEncodedString.class, key);
	    System.out.println("UserUnEncodedString==tmpUserUnEncodedString?"+(userUnEncodedString==tmpUserUnEncodedString));
	    Assert.assertTrue(userUnEncodedString==tmpUserUnEncodedString);

	    System.out.println("2 Query UserUnEncodedString with key_name1");
	    tmpUserUnEncodedString = null;
	    Query query = pm.newQuery(UserUnEncodedString.class);
	    query.setFilter("name == nameParam");
	    query.setOrdering("date desc");
	    query.declareParameters("String nameParam");
	    try {
	        List<UserUnEncodedString> results = (List<UserUnEncodedString>) query.execute("key_name1");
	        System.out.println("All UserUnEncodedString size:"+results.size());
	        Assert.assertEquals(1, results.size());
	        if (results.iterator().hasNext()) {
	            for (UserUnEncodedString e : results) {
	            	tmpUserUnEncodedString = e;
	                System.out.println(" "+e);
	            }
	        } else {
	        	System.out.println("2 Query get empty result");
	        }
	    } finally {
	        query.closeAll();
	    }
	    Assert.assertTrue(tmpUserUnEncodedString != null);
	    if (tmpUserUnEncodedString != null)
	    {
	    	System.out.println("3 Update the searched result[0] UserUnEncodedString with key_name1->KEY_NAME!");
	    	System.out.println("UserUnEncodedString==tmpUserUnEncodedString?"+(userUnEncodedString==tmpUserUnEncodedString));
	    	Assert.assertTrue(userUnEncodedString==tmpUserUnEncodedString);

	    	tmpUserUnEncodedString.setName("KEY_NAME!");
	    	pm.makePersistent(tmpUserUnEncodedString);
	    }

	    System.out.println("4 Delete UserUnEncodedString with key_name1");
	    System.out.println(" ->"+userUnEncodedString);
	    pm.deletePersistent(userUnEncodedString);

	    query = pm.newQuery(UserUnEncodedString.class);
	    int size = ((List<UserUnEncodedString>) query.execute()).size();
		System.out.println("All UserUnEncodedString size:"+size);
	    Assert.assertEquals(0, size);
	}

2.2.1 这里也没有特别的地方,值得稍微说一下的是我们使用未编码的字符串作为主键,则需要我们自己保证主键的键值是唯一的,否则makePersistent方法就是更新对象,而不是创建。这个方法pm.getObjectById(UserUnEncodedString.class, key);接受未编码的字符串为第二个参数用于查询。

2.3 com.google.appengine.api.datastore.Key

UserKey.java

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class UserKey {
     
 @PrimaryKey
 @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
 private Key key;
 @Persistent
 private Date date = new Date();
 @Persistent
 private String name;
 @Persistent
 private String type;
 
 public UserKey() {
  
 }
 public UserKey(String name, String type) {
  super();
  this.key = KeyFactory.createKey(UserKey.class.getSimpleName(), name);
  this.name = name;
  this.type = type;
 }

2.3.1 首先看这个POJO数据类,主键标识在下面这个字段上Key,参考GAE文档,它的键值包括父实体组(如果有)的键以及应用程序分配的字符串ID或系统生成的数字ID。要创建带应用程序分配的字符串ID的对象,请创建带有该ID的键值并将字段设置为该值。要创建带系统分配的数字ID的对象,请将键字段留为null。
这里我们选择使用程序自己分配字符串ID的Key,使用KeyFactory.createKey(String kind, String name)方法创建Key实例,第一个参数即上文提到的主键组成成分之一类型,第二个参数是标识这个主键的任意唯一的字符串,我的理解是和未编码字符串主键基本上一致。

TestUserWithKeyId.java

	@Test
	public void doTest() {

		PersistenceManager pm = PMF.get().getPersistenceManager();

		System.out.println("0 Create and persist UserLong with @PrimaryKey: Key");
		UserKey user = new UserKey("name1",UserKey.class.getSimpleName());
		pm.makePersistent(user);

		System.out.println("1 Query UserKey with getObjectById");
	    UserKey tmpUser = null;
	    Key key = user.getKey();

	    tmpUser = pm.getObjectById(UserKey.class, key);
	    System.out.println("(1)user==tmpUser ? "+(user==tmpUser));
	    Assert.assertTrue(user==tmpUser);

	    Key createdKey = KeyFactory.createKey(UserKey.class.getSimpleName(), user.getName());
	    tmpUser = pm.getObjectById(UserKey.class, createdKey);
	    System.out.println("(2)user==tmpUser ? "+(user==tmpUser));
	    Assert.assertTrue(user==tmpUser);

	    System.out.println("2 Query UserKey with name1");
	    tmpUser = null;
	    Query query = pm.newQuery(UserKey.class);
	    query.setFilter("name == nameParam");
	    query.setOrdering("date desc");
	    query.declareParameters("String nameParam");
	    try {
	        List<UserKey> results = (List<UserKey>) query.execute("name1");
	        System.out.println("All UserKey size:"+results.size());
	        Assert.assertEquals(1, results.size());

	        if (results.iterator().hasNext()) {
	            for (UserKey e : results) {
	            	tmpUser = e;
	                System.out.println(" "+e);
	            }
	        } else {
	        	System.out.println("2 Query get empty result");
	        }
	    } finally {
	        query.closeAll();
	    }
	    Assert.assertTrue(tmpUser != null);
	    if (tmpUser != null)
	    {
	    	System.out.println("3 Update the searched result[0] UserKey with name1->NAME!");
	    	System.out.println("user==tmpUser?"+(user==tmpUser));
	    	Assert.assertTrue(user==tmpUser);

	    	tmpUser.setName("NAME!");
	    	pm.makePersistent(tmpUser);
	    }

	    System.out.println("4 Delete UserKey with name1");
	    System.out.println(" ->"+user);
	    pm.deletePersistent(user);

	    query = pm.newQuery(UserKey.class);
	    int size = ((List<UserKey>) query.execute()).size();
		System.out.println("All UserKey size:"+size);
	    Assert.assertEquals(0, size);
	}

2.3.2 测试用例基本上和前面的一致,这里稍微值得说一下的是使用自己创建的key进行查询Key createdKey = KeyFactory.createKey(UserKey.class.getSimpleName(), user.getName());pm.getObjectById(UserKey.class, createdKey);本质上和上一种未编码字符串是一样的。

2.4 编码字符串形式的主键

UserEncodedString.java


@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class UserEncodedString {
     
 @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    @Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
    private String encodedKey;
 @Extension(vendorName="datanucleus", key="gae.pk-name", value="true")
    private String keyName;
 //"gae.pk-name" 字段可以在保存对象前设置为键名称。保存对象时,将使用包括键名称的完整键填充编码键字段。其类型必须为 String。
 @Persistent
 private Date date = new Date();
 @Persistent
 private String name;
 @Persistent
 private String type;
 
 public UserEncodedString() {
  
 }
 public UserEncodedString(String keyName, String name, String type) {
  super();
  this.encodedKey = KeyFactory.keyToString(KeyFactory.createKey(UserEncodedString.class.getSimpleName(), keyName));
  this.keyName = keyName;
  this.name = name;
  this.type = type;
 }

2.4.1 这个POJO数据存储类,更绕了一点点弯路,可以和上面的一种使用的Key作为主键的POJO数据类稍微对比一下看,然后就不会觉得太复杂了。
正如注释中所写
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
@Extension(vendorName=”datanucleus”, key=”gae.encoded-pk”, value=”true”)
private String encodedKey;

@Extension(vendorName=”datanucleus”, key=”gae.pk-name”, value=”true”)
private String keyName;
//”gae.pk-name” 字段可以在保存对象前设置为键名称。保存对象时,将使用包括键名称的完整键填充编码键字段。其类型必须为 String。

this.encodedKey = KeyFactory.keyToString(KeyFactory.createKey(UserEncodedString.class.getSimpleName(), keyName));
keyName字段只是用来产生使用程序自己分配字符串ID的Key的,根据这个字符串ID产生Key之后,将Key的编码字符串格式保存于encodedKey当中。

我的理解是可以encodedKey的作用==Key的作用,只是为了某些情况下使用方便。

TestUserWithEncodedStringId.java

	@Test
	public void doTest() {

		PersistenceManager pm = PMF.get().getPersistenceManager();

		System.out.println("0 Create and persist UserLong with @PrimaryKey: String encodedKey");
		UserEncodedString userEncodedString = new UserEncodedString("name1","name1",UserEncodedString.class.getSimpleName());
		pm.makePersistent(userEncodedString);

		System.out.println("1 Query UserEncodedString with getObjectById, using KeyFactory.stringToKey(encodedKey)");
	    UserEncodedString tmpUserEncodedString = null;
	    String encodedKey = userEncodedString.getEncodedKey();
	    System.out.println(userEncodedString);
	    Key key = KeyFactory.stringToKey(encodedKey);
	    System.out.println("KeyFactory.stringToKey(encodedKey)="+key);
	    tmpUserEncodedString = pm.getObjectById(UserEncodedString.class, key);
	    System.out.println("UserEncodedString==tmpUserEncodedString?"+(userEncodedString==tmpUserEncodedString));
	    Assert.assertTrue(userEncodedString==tmpUserEncodedString);

	    System.out.println("1(1) Query UserEncodedString with getObjectById, using KeyFactory.createKey(UserEncodedString.class.getSimpleName(), userEncodedString.getKeyName())");
	    key = KeyFactory.createKey(UserEncodedString.class.getSimpleName(), userEncodedString.getKeyName());
	    tmpUserEncodedString = pm.getObjectById(UserEncodedString.class, KeyFactory.keyToString(key));
		System.out.println(" (1)UserEncodedString==tmpUserEncodedString?"+(userEncodedString==tmpUserEncodedString));
		Assert.assertTrue(userEncodedString==tmpUserEncodedString);

	    System.out.println("1(2) Query UserEncodedString with getObjectById, using userEncodedString.getEncodedKey():"+userEncodedString.getEncodedKey());
	    tmpUserEncodedString = pm.getObjectById(UserEncodedString.class, userEncodedString.getEncodedKey());
	    System.out.println(" (2)UserEncodedString==tmpUserEncodedString?"+(userEncodedString==tmpUserEncodedString));
	    Assert.assertTrue(userEncodedString==tmpUserEncodedString);

	    System.out.println("2 Query UserEncodedString with name1");
	    tmpUserEncodedString = null;
	    Query query = pm.newQuery(UserEncodedString.class);
	    query.setFilter("name == nameParam");
	    query.setOrdering("date desc");
	    query.declareParameters("String nameParam");
	    try {
	        List<UserEncodedString> results = (List<UserEncodedString>) query.execute("name1");
	        System.out.println("All UserEncodedString size:"+results.size());
	        Assert.assertEquals(1, results.size());

	        if (results.iterator().hasNext()) {
	            for (UserEncodedString e : results) {
	            	tmpUserEncodedString = e;
	                System.out.println(" "+e);
	            }
	        } else {
	        	System.out.println("2 Query get empty result");
	        }
	    } finally {
	        query.closeAll();
	    }
	    Assert.assertTrue(tmpUserEncodedString != null);
	    if (tmpUserEncodedString != null)
	    {
	    	System.out.println("3 Update the searched result[0] UserEncodedString with name1->NAME!");
	    	System.out.println("UserEncodedString==tmpUserEncodedString?"+(userEncodedString==tmpUserEncodedString));
	    	Assert.assertTrue(userEncodedString==tmpUserEncodedString);

	    	tmpUserEncodedString.setName("NAME!");
	    	pm.makePersistent(tmpUserEncodedString);
	    }

	    System.out.println("4 Delete UserEncodedString with name1");
	    System.out.println(" ->"+userEncodedString);
	    pm.deletePersistent(userEncodedString);

	    query = pm.newQuery(UserEncodedString.class);
	    int size = ((List<UserEncodedString>) query.execute()).size();
		System.out.println("All UserEncodedString size:"+size);

	    Assert.assertEquals(0, size);
	}

2.4.2 测试用例还是和前面的一致,这里要说的就是下面三个使用getObjectById的查询结果是一致的。
pm.getObjectById(UserEncodedString.class, userEncodedString.getEncodedKey());
第一个使用Key的编码字符串格式直接查询

String encodedKey = userEncodedString.getEncodedKey();
Key key = KeyFactory.stringToKey(encodedKey);
pm.getObjectById(UserEncodedString.class, key);
第二个使用KeyFactory.stringToKey方法根据key查询

key = KeyFactory.createKey(UserEncodedString.class.getSimpleName(), userEncodedString.getKeyName());
pm.getObjectById(UserEncodedString.class, KeyFactory.keyToString(key));
第三个使用程序自己分配字符串ID生成的Key来查询

3. 总结

本文介绍了使用GAE存取数据的基本方法,主要展开并演示了使用4种主键的数据对象存取方法,属于GoogleAppEngine的入门级别文章。

对象关系,事务等相关内容也是构建GAE应用必备的,然而GAE实例代码中并没有很多现成的例子,我瞄到过GAE DataNucleus插件中有些单元测试代码可以参考,因为没有强烈的需求,我还没有深入研究,目前我也只是处于简单的1对1,1对N关系搭起来能用的阶段。

GoogleAppEngine

GoogleAppEngine中如何使用EL表达式?

四 28th

由Jacky发表在GoogleAppEngine

没有评论

GoogleAppEngine中如何使用EL表达式?

方法是必须在页面里强制指定:

<%@ page language=”java” contentType=”text/html; charset=UTF-8″ pageEncoding=”UTF-8″  isELIgnored=”false”%>

我也尝试在web.xml中配置了el-ignore:false,但是这样做不行。

 This doesn't work for GAE
 <jsp-config>
  <jsp-property-group>
   <url-pattern>/*</url-pattern>
   <el-ignored>false</el-ignored>
  </jsp-property-group>
 </jsp-config>

 

除此之外,还要在WEB-INF/lib中加入JSTL,EL的jar文件。

update:自己加入JSTL和EL的jar文件可能会导致这个问题 java.lang.AbstractMethodError: javax.servlet.jsp.PageContext.getELContext()Ljavax/el/ELContext;

但是如果不加入,使用eclipse编辑jsp的时候不能对JSTL进行代码辅助提示,权衡一下把jar加入classpath后运行app时还需要去掉,否则会有兼容性问题。

有用的参考资料:JavaEE的相关技术在Google App Engine中的兼容情况。

EL, GoogleAppEngine, jsp

Rate limit exceeded. Clients may not make more than 150 requests per hour

四 22nd

由Jacky发表在GoogleAppEngine

没有评论

本文描述”Rate limit exceeded. Clients may not make more than 150 requests per hour” 的解决办法

昨天做空姐的twitter自动翻译+Retweet程序的时候遇到的问题(使用GAE+twitter4j+google translate API)

调用twitter4j API去查询空姐最新状态的时候,一直报这个错 “Rate limit exceeded. Clients may not make more than 150 requests per hour”,月光博客也遇到过这个问题,认为这个是GAE IP不是白名单的问题,难道没有解决办法了?

有很多基于GAE的twitter应用,如果大家都这样那岂不是要疯掉了。。。所以我想肯定不关白名单啥事情,一定有别的解决办法。

后来我在google code里查到一条类似的issue ,幸运的是已经fix了

所以这个问题解决办法就是:修改searchURL为 http://api.twitter.com/1/search.json (实效性不能保证,至少目前这个方法works)

如果遇到问题的各位童鞋也用的是twitter4j,大家会发现twitter4j jar已经封装好了,默认的searchBaseURL是http://search.twitter.com/search.json,我看了一下twitter4j的源码,对于[基于GAE的]web项目,我们可以在WEB-INF/下面新建一个twitter4j.properties,然后增加这一行searchBaseURL=http://api.twitter.com/1/

aoi_sola, google, GoogleAppEngine, twitter

学车日志(4)

四 19th

由Jacky发表在曾经的一天

1个评论

小路抽到限宽门,上车的时候倒是蛮自信的

第一次侧方停车,语音提示说考试结束,我晕,看提示屏幕说后车身出线

开过去再来一次,又停了一次侧方,这次没语音提示了,然后一路开到底没啥问题,结果没有提示通过,原来这一次侧方没成绩

又开过去补一次侧方停车,停的感觉蛮好的,拉手刹准备开出去的时候,又TMD的叫了,考试结束

 

郁闷,严重怀疑考试系统有问题,自己感觉的确停的很好啊

心情很不爽,开出去了,后来想想真后悔自己没有下车看看到底有没有后车身出线

唉,鄙视自己挂在侧方停车上了,太鄙视自己了

学车
20jQueryMethods

你应该使用的20个有用的jQuery方法

四 15th

由Jacky发表在JavaScript

没有评论

原文地址:20 Helpful jQuery Methods you Should be Using
原文作者:Andrew Burgess

这篇文章介绍了jQuery中有用的20个方法,1-20编号如下,本文没有对照原文逐句翻译,这个20个方法名称,我其实就是用下面第14个map()的方式取出来的,亮点如下,firebug真是个离不开的好工具。

说到工具,那就顺便再提一下一个简洁的jQuery代码调试工具:jQueryPad

支持代码高亮,不支持代码辅助,小问题是这个工具带的jQuery.js的版本旧了,需要自己更新一份到它的目录。

(当然,我还是最喜欢firebug,特别是网页复杂的时候)

1 after() / before()

这个无需多说了,上面的图就是个例子,就是在当前对象的前面或者后面插入内容,被插入的内容是当然对象的兄弟节点

2 change()

和click()或者hover()一样,它也是个事件处理器,这个change事件作用于 textarea, text input, select, 当目标元素的值改变的时候触发该事件。它不同于对象失去焦点时触发的事件foucusOut()和blur()。

change()事件对于客户端验证的任务最适合不过了,因为你不需要在输入字段值没有变化的时候重新验证字段。

$(document).ready(
    function() {
$('input[type=text]').change(function () {
    switch (this.id) {
/* some validation code here */
case "name":
alert(this.value+"....");
break;
default:
alert(this.id);
break;
    }
});
    });

// HTML Form
Name: <input id="name" type="text"/>

3 Context

在jQuery中,使用选择器,其实默认作用于document这个上下文上

了解了这一点话,Context就简单了,它是一个属性兼参数

作为属性:每个jQuery对象,都有个context属性

作为参数:当使用jQuery选择器时,可以使用第二个参数context,细化了查询的范围

用Firebug试验一下就明白了!

4 data() / removeData()

在元素上(确切的说应该是jQuery对象)上存储数据的一种办法

5 queue() / dequeue()

用在jQuery动画效果上,可以方便的增加或者移除一个特效

6 delay()

暂停一段时间的动画效果

7 bind(), unbind(),live(), and die()

以前的文章说过了,不废话了

8 eq()

用数组下标的方式取得一组元素中的某一个

9 get()

取得jQuery元素对应的Dom对象格式

10 grep()

这个我挺喜欢,第二个参数是个用于过滤的回调函数,很容易懂的

 var nums = '1,2,3,4,5,6,7,8,9,10'.split(',');

 nums = $.grep(nums, function(num, index) {
   // num = the current value for the item in the array
   // index = the index of the item in the array
   return num > 5; // returns a boolean
 });

 console.log(nums) // 6,7,8,9,10

11 Pseudo-Selectors

$(':animated'); // returns all elements currently animating
$(':contains(me)'); // returns all elements with the text 'me'
$(':empty'); // returns all elements with no child nodes or text
$(':parent'); // returns all elements with child nodes or text
$('li:even'); // returns all even-index elements (in this case, <li>s)
$('li:odd'); // can you guess?
$(':header'); // returns all h1 - h6s.
$('li:gt(4)'); // returns all elements with an (zero-based) index greater than the given number
$('li:lt(4)'); // returns all element with an index less than the given number
$(':only-child'); // returns all . . . well, it should be obvious

12 isArray() / isEmptyObject() / isFunction() / isPlainObject()

 $.isArray([1, 2, 3]); // returns true
 $.isEmptyObject({}); // returns true
 $.isFunction(function () { /****/ }); // returns true

 function Person(name) {
     this.name = name
     return this;
 }
 $.isPlainObject({})); // returns true
 $.isPlainObject(new Object()); // returns true
 $.isPlainObject(new Person()); // returns false

13 makeArray()

使用jQuery选择器返回的都是jQuery对象,所以$.makeArray()变得挺实用的

    var ps = $('p');
    $.isArray(ps); //returns false;
    ps = $.makeArray(ps);
    $.isArray(ps); // returns true;

14 map()

上面firebug的贴图中延伸过了,自己试试看吧

15 parseJSON()

把JSON格式的字符串结果解析成JSON对象

16 proxy()

$.proxy() 解决了函数调用的上下文问题

 var person = {
   name : "Andrew",
   meet : function () {
     alert('Hi! My name is ' + this.name);
   }
 };
 person.meet();   // 正常工作
 $('#test').click(person.meet); // 返回 Hi! My name is undefined 或者 Hi! My name is 空

$('#test').click($.proxy(person.meet, person));
// we could also do $.proxy(person, "meet")

17 replaceAll() / replaceWith()

用来替换内容的,replaceWith更顺脑

<div class="error1">error section 1</div>
<br/>
<div class="error2">error section 2</div>
<br/>
$('<span class=fixed>The error has been corrected</span>').replaceAll('.error1');
"$('.error2').replaceWith('<span class=fixed>The error has been corrected</span>');

18 serialize() / serializeArray()

看例子吧,易懂

 <form>
     <input type="text" name="name" value="John Doe" />
     <input type="text" name="url" value="http://www.example.com" />
 </form>
console.log($('form').serialize());​​​ // logs : name=John+Doe&url=http%3A%2F%2Fwww.example.com
console.log($('form').serializeArray());
// logs : [{ name : 'name', value : 'John Doe'} , { name : 'url', value : 'http://www.example.com' } ]

19 siblings()

返回所有兄弟节点

20 wrap() / wrapAll() / wrapInner()

在作用对象的外面,周围,或者里面,包一层东西

Conclusion

好好玩这些个方法吧!

…

JavaScript, jQuery, web开发, 翻译

美剧更新自动提醒服务

四 6th

由Jacky发表在Notifier

没有评论

美剧自动提醒服务: http://jhnotifier.appspot.com/

源码host在: http://code.google.com/p/jhnotifier/

设想是做成一个通用的通知系统框,基于这个框架可以开发出很多爬网页发通知(邮件,短信)的应用

当前这个服务可以配置关心的Easytv上的美剧更新,但是仅支持向我自己的邮箱发邮件提醒

计划增加的功能:

1. 短信提醒

2. 支持任意用户,对关心的Easytv美剧提交自己的信息,收到更新通知

3. 增加其他的爬网页,发通知服务(例如美剧论坛BT种子更新,天气预报,或者是定制程度更高的应用)

欢迎有兴趣的加入这个项目,重构代码,加入更多的提醒服务!

系统主要结构也比较简单:

所有的爬网页任务实现ObservableTask接口,在doTask方法里实现逻辑,notify Observer

程序预先实现好的Observer有:Email通知,SMS通知(暂时只是框架代码)

public abstract class ObservableTask extends Observable{
public abstract void doTask() throws Exception;
}

public class EmailNotifier extends Notifiable implements Observer {
private static final Logger log = Logger.getLogger(EmailNotifier.class.getName());
public void update(Observable o, Object arg) {
for (User user : this.getNotifierList()) {
AppEngineEmailSender.send(user.email, ((Entity)arg).emailSubject(), ((Entity)arg).emailBodyText());
}
}
} 

做Easytv这个提醒服务的时候,开始是基于CLI的,后来玩了一下Google App Engine,目前对它的logging还不是100%清楚,不知道它怎么映射日志级别的,然后还有点数据存储服务的代码,一对多玩起来了,但是不全明白,貌似和EJB3还是有点不一样,有空了再玩吧!

google, GoogleAppEngine, Notifier
«12345»...最后一页 »
  • 为了节约各位宝贵的时间,允许看帖不回帖!

    如果哪篇日志实在打动你,请留下宝贵意见!

    • 最新评论
    • 热门文章
    • 存档
    • 标签
    • 分类
    • CSS (4)
    • GoogleAppEngine (5)
    • iPhone (3)
    • JavaEE (6)
    • JavaScript (11)
      • jQuery (1)
    • Notifier (2)
    • PHP (2)
    • Presentation (1)
    • python (1)
    • Struts (1)
    • Unix/Linux (4)
    • WebDev (8)
    • WordPress (1)
    • 大事件 (2)
    • 小问题 (6)
    • 曾经的一天 (20)
    • 杂项 (2)
    • 站长日志 (4)
    分页 失败 学车 感想 插件 生活 网事 翻译 高手必备 AJAX avatar baidu checksum comment CSS EL google GoogleAppEngine gravatar http iPhone java JavaScript jboss jndi jQuery jsp lyrics md5 movie mplayer Notifier python Q&A qq reflection screen SEO sevlet shell Struts ubuntu usability web开发 WordPress
    • 2010年九月 (1)
    • 2010年八月 (2)
    • 2010年七月 (2)
    • 2010年六月 (5)
    • 2010年五月 (5)
    • 2010年四月 (7)
    • 2010年三月 (4)
    • 2010年二月 (6)
    • 2010年一月 (12)
    • 2009年十二月 (8)
    • 2009年三月 (1)
    • 2009年二月 (4)
    • 2008年十一月 (1)
    • 2008年十月 (2)
    • 2008年九月 (1)
    • 在火车站被一个小孩抱住腿 (4)
    • Working with Events, Part 3: More Event Delegation with jQuery (3)
    • TeamName SilverBullet (3)
    • Hello world! (2)
    • [WebDev]有用的http-equiv属性 (2)
    • Working with Events, part 1 (2)
    • 阴差阳错 (2)
    • 惭愧,我也做了一回痴情的苹果粉丝 (2)
    • 久疏战阵 (2)
    • AVATAR (1)
    • awings: 难怪Apple无往而不利。
    • awings: 呵呵,如果事先通知他们一下的话,可能就不会有这个小麻烦了。
    • Rockie: ...
    • 昵称(必须)任鸟飞: 挺悲哀的,没必要记那么多,知道有那么回事就可以。中国人确实很迂腐。
    • very: 支持下,很强大
    • bigCat: 去捆绑paypal王道
    • 行者: 呵呵,从百度过来的,搜索一个问题。 博客不错,还是Media Temple的主机,价格挺贵的~ 我是做前端的,欢迎回访,可以的话,做个友情链接。...
    • larry: 同情,考试蛮刺激的 ;-)
  • Calendar

    2010年九月
    一 二 三 四 五 六 日
    « 八    
     12345
    6789101112
    13141516171819
    20212223242526
    27282930  
  • 最近文章

    • Inception
    • link的地址以斜线开头完全没有道理啊
    • 久疏战阵
    • 追求完美引发的杯具
    • 惭愧,我也做了一回痴情的苹果粉丝
  • My Buzz
    Inception
    2:42 PM Sep 06, 2010, comment
    iambad: RT @rtmeme: RT @Cattyhouse RT @rebecca_kidult: 转《盗梦空间》引进国内号称一刀未剪,主要原因是电影局那帮人的智商,实在不知道从哪里剪起。对此,我深信不疑。
    1:28 AM Sep 03, 2010, comment
    iambad: RT @rtmeme: RT @BaoliDao RT @yimaobuba: 汤唯终于因为在贱党伟业饰演杨开慧被解禁,中宣部表示:这一举动表达了汉奸睡得,主席为啥睡不得的正确思想取向
    1:26 AM Sep 03, 2010, comment
    iambad: RT @rtmeme: RT @tonyq0802 RT @Cattyhouse: 我知道了,原来苹果中国的价格直接是美国价格乘以8,汇率无效。
    1:23 AM Sep 03, 2010, comment
    iambad: RT @ceoSteveJobs: There's no camera on the iPod nano. Why? Because our design team wanted to put it behind the clip. Yeah, real brilliant.
    1:22 AM Sep 03, 2010, comment
    iambad: Test post a photo ☺ http://moby.to/k2mm6y
    7:12 AM Sep 02, 2010, comment
    iambad: RT @rtmeme: RT @chouti: 同事问,新的iPod Touch和iPhone4有什么区别? 回答,通过移除电话模块修复了信号门的Bug -___-
    4:21 AM Sep 02, 2010, comment
    iambad: RT @rtmeme: RT @tenione: 乔帮主把nano拦腰截断就变成了新的nano和shuffle
    4:21 AM Sep 02, 2010, comment
    shunz: 对比了最新版的城际通、凯立德、道道通、高德以及灵图等GPS地图以后,发现还是城际通的地图最新最全,但是软件的设计是凯立德更人性化一点。
    9:13 AM Sep 01, 2010, comment
    iambad: RT @rtmeme: RT @duanzi: Via @oopsimtim: 今天坐地铁,人还不算多,某一站时进来一个男生,身材看起来比较胖一点…他进来之后地铁就发出“滴~~滴~~滴~~”的关门警告声。 接着他一下子突然就跳到站台上去了…看着地铁门关上,他嘴里喊着着: ...
    7:25 AM Sep 01, 2010, comment
     
  • RSS 最近的分享

    • 看看都是什么人在反三俗 2010/09/06
    • rtmeme: RT @luogl RT @nuosong 新西兰地震是一场有组织、有预谋、别有用心的反华地震,这种卑鄙的行径,极大地伤害了中国人民的感情! 2010/09/06
    • rtmeme: RT @bafield RT @ranyunfei: 新西兰七点二级地震只重伤了两个人,这也好叫地震,我呸!只有中国的地震才叫真正的地震,不死个成千上万,不符合官方“多难兴邦”的期待,那地震的党性原则到哪去了? 2010/09/06
    • rtmeme: RT @damyata RT @glimho: 新西兰大地震无人死亡,开不成表彰大会了,没那么多英雄事迹了,拍不成电影了,捐不了款了,不能降半旗了,不能全国不打网游了,明星也不能诈捐了,也没法创造生命奇迹了!最主要是新西兰错过多难兴邦的大好形势了 2010/09/06
    • [视频]看武侠片如何欺骗你的眼睛 2010/09/06
    • 特种部队级别的国外的冲关冒险节目 2010/09/06
    • 住这里也不错,瑞典波音747青年旅社 2010/09/06
    • 哇靠 这个是才是人人网最佳形象代言人啊!! 2010/09/03
    • rtmeme: RT @ioola RT @localhost_8080: 昨晚梦见男朋友和别的女人在逛街,梦里我的第一反应是查源代码…结果调试半天查不出来为什么显示的是那个女人不是我,最后含泪把那个女人给注释掉了,再一运行就是我男朋友自己逛街了…醒来字脸呆了很久…rz 2010/09/03
    • rtmeme: RT @tengbiao RT @c06658: 韩寒:我们的领导群从这一批换成了那一批,治国口号从这一堆换成了那一堆,丰功伟绩从这个会变成了那个会,社会悲剧只是从这个人变成了那个人 2010/09/03
  • Tag Cloud

    分页 失败 学车 感想 插件 生活 网事 翻译 高手必备 AJAX avatar baidu checksum comment CSS EL google GoogleAppEngine gravatar http iPhone java JavaScript jboss jndi jQuery jsp lyrics md5 movie mplayer Notifier python Q&A qq reflection screen SEO sevlet shell Struts ubuntu usability web开发 WordPress
  • 我的足迹

    • 我的GoogleReader分享
  • 链接表

    • code's life
    • JeffChen在胡言乱语
    • Man can be an artist in anything
    • Shadow & Honnix
    • snow river and er
    • tdsparrow
    • 梦许之地
    • 老茂博客
  • 用户登录






    • 忘记你的密码?
版权所有 © 2010 haojii | 由digitalnature提供主题Mystique | 由WordPress强力驱动
RSS订阅 XHTML 1.1 顶端