我正在尝试App Engine,使用JDO和DataNucleus进行持久化。我有一个包含多个单向关系的简单域。问题在于嵌套这些关系:
根据DataNucleus Documentation,持久性可达性语义应该通过级联文明的持久性来保留所有内容。我有一个JUnit测试来检查这些对象的基本存储和检索,但它的行为是不一致的。如果不对代码进行任何更改,则重复运行测试会产生不确定的结果。具体来说,军队只有50%的时间存在。它们是唯一失败的测试。
我可以更容易地理解军队永远不会持续存在的情况,但不规则的行为使我感到茫然。其他所有东西都能正确而持续地存在。我尝试在事务中包装工厂方法,我尝试了双向关系,并且都没有更改JUnit中的50/50通过/失败拆分。
我正在使用基于注释的DataNucleus配置,如App Engine文档中所述(由于反垃圾邮件措施而未包含链接)。我为附加的大量代码道歉;我只是不知道我哪里出错了。
CivilizationCreateTest.java:
package com.moffett.grunzke.server;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.moffett.grunzke.generic.GenericHelperFactory;
import com.moffett.grunzke.server.civilization.Army;
import com.moffett.grunzke.server.civilization.Civilization;
import com.moffett.grunzke.server.civilization.Clan;
import com.moffett.grunzke.server.civilization.Land;
import com.moffett.grunzke.server.civilization.Military;
import com.moffett.grunzke.server.civilization.Settlement;
@SuppressWarnings("unchecked")
public class CivilizationCreationTest
{
private final LocalServiceTestHelper helper = new LocalServiceTestHelper(
new LocalDatastoreServiceTestConfig(),
new LocalUserServiceTestConfig())
.setEnvIsLoggedIn(true)
.setEnvEmail("generic.user@gmail.com")
.setEnvAuthDomain("google.com");
@Before
public void setUp()
{
helper.setUp();
}
@After
public void tearDown()
{
helper.tearDown();
}
@Test
public void testCivilizationCreation()
{
String clanName = "Test Clan";
String rulerName = "Test Ruler";
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
if (user == null)
{
fail("No user");
}
PersistenceManager pm = PMF.get().getPersistenceManager();
CivilizationFactory.newInstance(user, clanName, rulerName);
// We check to make sure that 1, and only 1 Civilization has been made.
Query q1 = pm.newQuery("SELECT FROM " + Civilization.class.getName());
List<Civilization> allCivilizations = (List<Civilization>) q1.execute();
assertTrue(allCivilizations.size() == 1);
// Now we move on to checking the other aspects.
Civilization persistentCiv = allCivilizations.get(0);
Clan persistentClan = persistentCiv.getClan();
Land persistentLand = persistentCiv.getLand();
Military persistentMilitary = persistentCiv.getMilitary();
ArrayList<Settlement> persistentSettlements = persistentCiv.getSettlements();
// Make sure Civ has pointers to all the necessary elements.
assertTrue(persistentClan != null);
assertTrue(persistentLand != null);
assertTrue(persistentMilitary != null);
assertTrue(persistentMilitary.getArmies() != null);
assertTrue(persistentSettlements != null);
// Lastly we want to make sure that there is only one entry in each of Clan,
// Land, Military, Army, Settlement.
Query q2 = pm.newQuery("SELECT FROM " + Clan.class.getName());
List<Clan> allClans = (List<Clan>) q2.execute();
assertTrue(allClans.size() == 1);
Query q3 = pm.newQuery("SELECT FROM " + Land.class.getName());
List<Land> allLand = (List<Land>) q3.execute();
assertTrue(allLand.size() == 1);
Query q4 = pm.newQuery("SELECT FROM " + Military.class.getName());
List<Military> allMilitary = (List<Military>) q4.execute();
assertTrue(allMilitary.size() == 1);
Query q5 = pm.newQuery("SELECT FROM " + Army.class.getName());
List<Army> allArmy = (List<Army>) q5.execute();
// *** THIS FAILS 50% OF THE TIME ***
assertTrue(allArmy.size() == 1);
Query q6 = pm.newQuery("SELECT FROM " + Settlement.class.getName());
List<Settlement> allSettlement = (List<Settlement>) q6.execute();
assertTrue(allSettlement.size() == 1);
}
}
CivilizationFactory.java:
package com.moffett.grunzke.server;
import java.util.ArrayList;
import com.google.appengine.api.users.User;
import com.moffett.grunzke.server.civilization.Army;
import com.moffett.grunzke.server.civilization.Civilization;
import com.moffett.grunzke.server.civilization.Clan;
import com.moffett.grunzke.server.civilization.Land;
import com.moffett.grunzke.server.civilization.Military;
import com.moffett.grunzke.server.civilization.Settlement;
public class CivilizationFactory
{
public static Civilization newInstance(User user, String clanName, String rulerName)
{
// First we make a new clan.
Clan clan = new Clan();
clan.setUser(user);
clan.setClanName(clanName);
clan.setRulerName(rulerName);
// Don't need land.save() because of persistence-by-reachability
// Now we need to make a new Land.
Land land = new Land();
land.setArableLand(100);
land.setPasturableLand(0);
land.setLandUsedBySettlements(0);
// Don't need land.save() because of persistence-by-reachability
// Now we need to make a new Military
Military military = new Military();
Army army = new Army();
army.setMeleeUnits(10);
army.setRangedUnits(10);
army.setMountedUnits(10);
military.addArmy(army);
// Don't need military.save() because of persistence-by-reachability
// Now we need to make a new Settlement
Settlement settlement = new Settlement();
// Don't need settlement.save() because of persistence-by-reachability
ArrayList<Settlement> settlements = new ArrayList<Settlement>();
settlements.add(settlement);
// Lastly join everything together in the civ
Civilization civ = new Civilization();
civ.setClan(clan);
civ.setLand(land);
civ.setMilitary(military);
civ.setSettlements(settlements);
civ.save();
// civ.save should casacde to cover all of the elements above
return civ;
}
}
Civilization.java:
package com.moffett.grunzke.server.civilization;
import java.util.ArrayList;
import javax.jdo.PersistenceManager;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;
import com.moffett.grunzke.server.PMF;
@PersistenceCapable
public class Civilization
{
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private Clan clan;
@Persistent
private Land land;
@Persistent
private Military military;
@Persistent
private ArrayList<Settlement> settlements = new ArrayList<Settlement>();
public void save()
{
PersistenceManager pm = PMF.get().getPersistenceManager();
try
{
pm.makePersistent(this);
}
finally
{
pm.close();
}
}
public ArrayList<Settlement> getSettlements()
{
return settlements;
}
public void setSettlements(ArrayList<Settlement> settlements)
{
this.settlements = settlements;
}
public Key getKey()
{
return key;
}
public void setKey(Key key)
{
this.key = key;
}
public Clan getClan()
{
return clan;
}
public void setClan(Clan clan)
{
this.clan = clan;
}
public Land getLand()
{
return land;
}
public void setLand(Land land)
{
this.land = land;
}
public void setMilitary(Military military)
{
this.military = military;
}
public Military getMilitary()
{
return military;
}
}
Military.java
package com.moffett.grunzke.server.civilization;
import java.util.ArrayList;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;
@PersistenceCapable
public class Military
{
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private ArrayList<Army> armies = new ArrayList<Army>();
public Key getKey()
{
return key;
}
public void setKey(Key key)
{
this.key = key;
}
public ArrayList<Army> getArmies()
{
return armies;
}
public void setArmies(ArrayList<Army> armies)
{
this.armies = armies;
}
public void addArmy(Army army)
{
this.armies.add(army);
}
}
Army.java
package com.moffett.grunzke.server.civilization;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;
@PersistenceCapable
public class Army
{
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private int meleeUnits;
@Persistent
private int rangedUnits;
@Persistent
private int mountedUnits;
public Key getKey()
{
return key;
}
public void setKey(Key key)
{
this.key = key;
}
public int getMeleeUnits()
{
return meleeUnits;
}
public void setMeleeUnits(int meleeUnits)
{
this.meleeUnits = meleeUnits;
}
public int getRangedUnits()
{
return rangedUnits;
}
public void setRangedUnits(int rangeUnits)
{
this.rangedUnits = rangeUnits;
}
public int getMountedUnits()
{
return mountedUnits;
}
public void setMountedUnits(int mountedUnits)
{
this.mountedUnits = mountedUnits;
}
}
答案 0 :(得分:0)
我的猜测是问题在于你设置实现List
的setter方法的方式。请记住,JDO将使用持久感知版本替换ArrayList字段,因此您不希望更改字段。试试这个:
@PersistenceCapable
public class Military {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private final List <Army> armies = new ArrayList<Army>();
public void setArmies(List<Army> armies) {
this.armies.clear();
this.armies.addAll(armies);
}
这也是出于其他原因的好主意。你不希望有人这样做:
military.setArmies(armies);
armies.clear();
......或者这个:
military.getArmies().clear();
就个人而言,我会让改变实体的方法只公开你想要的操作:
public void addArmy(Army army) {
armies.add(army);
}
public List<Army> getArmies() {
return Collections.unmodifiableList(armies);
}