Project Looking Glass, Sun's Java 3D desktop (Vista Killer) has finally released 1.0.0, and of course I had to check it out. Here's a screenshot:

@Transactional
public void registerNewUser(User user) throws LDAPException {
log.info("Registering user: " + user);
userDao.insertUser(user);
ldapService.registerEntry(user.getFirstName(), user.getLastName()); // Let's assume this throws a (checked) LDAPException
}
@Transactional
public void registerNewUser(User user) {
log.info("Registering user: " + user);
userDao.insertUser(user);
try {
ldapService.registerEntry(user.getFirstName(), user.getLastName()); // Let's assume this throws a (checked) LDAPException
} catch (LDAPException e) {
// Log and move on
log.error("LDAP registration failed: " + e.getMessage());
}
}
However, please note that the Spring Framework's transaction infrastructure code will, by default, only mark a transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception an instance or subclass of RuntimeException. (Errors will also - by default - result in a rollback.) Checked exceptions that are thrown from a transactional method will not result in the transaction being rolled back.
Note the very last sentence (my bold). Checked exceptions do not trigger a transaction rollback!
public class AbstractDaoTest {
SessionFactory sessionFactory;
public AbstractDaoTest(Class... classes)
{
// Read hibernate.cfg.xml from root of classpath, for example src/test/resources
Configuration configuration = new Configuration();
for (Class clazz : classes) {
configuration.addClass(clazz);
}
sessionFactory = configuration.buildSessionFactory();
}
}
public class ParentDaoTest extends AbstractDaoTest {
ParentDao dao;
public ParentDaoTest() {
// Parent has a relation to the Child class, so we need two classes mapped
super(Parent.class, Child.class);
}
public void setParentDao(ParentDao dao)
this.dao = dao;
}
public void testSomething() {
// Stuff that involves Parent and Child
}
}
Min katt Morrissey dog idag. Han hade en medfödd, obotlig njursjukdom som till slut blev för svår att uthärda, och han somnade in på sin lurviga fäll hemma i soffan efter en överdos sömnmedel.
Tack Yvonne på Djurdoktorn för all hjälp.
Lillmosse, 2002-07-04 - 2006-12-07
Listlist = new ArrayList ();
list.add(Integer.valueOf(1));
list.add(Integer.valueOf(2));
list.add(Integer.valueOf(3));
Listlist = Arrays.asList(1, 2, 3);
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:postgresql:myapp_test"/>
<property name="driverClassName" value="org.postgresql.Driver"/>
<property name="username" value="my_db_user"/>
<property name="password" value="******"/>
</bean>
public abstract class AbstractDaoIntegrationTest extends AbstractTransactionalDataSourceSpringContextTests {
SessionFactory sessionFactory;
SimpleJdbcTemplate jt;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
protected String[] getConfigLocations() {
return new String[] {
"applicationContext-dao.xml",
"applicationContext-env.xml"
};
}
@Override
protected void onSetUpBeforeTransaction() throws Exception {
jt = new SimpleJdbcTemplate(jdbcTemplate);
}
@Override
protected void onSetUpInTransaction() throws Exception {
super.onSetUpInTransaction();
// Load a batch of test data in the transaction
executeSqlScript("classpath:test-data.sql", false);
}
// Utility method
protected void flush() {
sessionFactory.getCurrentSession().flush();
}
}
public class EmployeeDaoTest extends AbstractDaoIntegrationTest {
EmployeeDao employeeDao;
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
}
public void testLoadEmployee() throws Exception {
Employee employee = employeeDao.load(7L);
assertEquals("197708170000", employee.getUsername());
assertEquals("test-password", employee.getPassword());
assertEquals("test.realm", employee.getRealm());
assertEquals("197708170000@test.realm", employee.getJid());
assertEquals("test-firstName test-familyName", employee.getName());
// Assert every property
}
public void testStore() throws Exception {
Employee employee = new Employee();
employee.setUsername("197708306621");
employee.setPassword("_password");
employee.setRealm("_realm");
employee.getRoles().add(User.Role.CUSTOMER);
employee.getRoles().add(User.Role.EMPLOYEE);
employee.getRoles().add(User.Role.TRANSLATOR);
employee.getVcard().setFirstName("_firstName");
employee.getVcard().setFamilyName("_familyName");
// Store
employeeDao.store(employee);
// Flush manually, to make sure the SQL is issued
flush();
// Start assertions by loading data from the database using the JdbcTemplate
assertNotNull(employee.getId());
Map authregMap = jt.queryForMap(
"select * from authreg where id = ? " +
"and class = 'se.petrix.iaba.model.user.Employee'", employee.getId());
assertEquals("197708306621", authregMap.get("username"));
assertEquals("_password", authregMap.get("password"));
assertEquals("_realm", authregMap.get("realm"));
// Roles
List<Map<String,Role>> roles = jt.queryForList(
"select role from authreg_roles where authreg ? order by role",
employee.getId());
assertEquals(3, roles.size());
assertEquals(Role.CUSTOMER.name(), roles.get(0).get("role"));
assertEquals(Role.EMPLOYEE.name(), roles.get(1).get("role"));
assertEquals(Role.TRANSLATOR.name(), roles.get(2).get("role"));
}
public void testDelete() throws Exception {
employeeDao.delete(7L);
flush();
// Employee
assertEquals(0, jt.queryForInt("select count(*) from authreg where id = 7"));
// Roles
assertEquals(0, jt.queryForInt("select count(*) from authreg_roles where authreg_id = 7"));
}
public void testEmployeeRelations() throws Exception {
Employee employee = employeeDao.load(7L);
// Manually end the transaction, which closes the Hibernate session
endTransaction();
try {
Role role = employee.getRoles().iterator.next();
// Success
} catch (LazyInitializationException e) {
fail("Employee.roles relation should not be lazy");
}
try {
Customer customer = employee.getCustomers().iterator.next();
fail("Employee.customers relation should be lazy");
} catch (LazyInitializationException e) {
// Success
}
}
svn co http://svn.apache.org/repos/asf/maven/continuum/tags/continuum-1.0.3/
Some tests failed, and I didn't have the time to investigate further, and one of the modules failed to find the app.properties file automatically. Also, you may need to download and install some Sun jars manually, but that's not a big problem since Maven hands you a command line to use.
mvn -Dmaven.test.skip=true -DappProperties=app.properties install
puts "Hello, World!"
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
<hibernate3:sessionFactory mappedClasses="se.petrix.cookbook.model..*"/>
<aop:aspectj-configured configuredClasses="se.petrix.cookbook.model..*"/>
<aop:aspectj-transactions/>
public static Recipe load(Long id) { ... }
Recipe r = Recipe.load(1L);
public static Recipe load(Long id) {
return new Recipe().doLoad(id);
}
public static T load(Long id) {
return T.class.newInstance().doLoad(id);
}
// 1:
Category c = new Category().load(1L);
// 2:
Category c = new Category(1L).load();
// 3:
Category c = new Category(); c.setId(1L); c.load();
// 4: (perform a load operation in the constructor)
Category c = new Category(1L);
public abstract class CookbookModelTest extends AbstractTransactionalDataSourceSpringContextTests {
SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
protected String[] getConfigLocations() {
return new String[] {
"applicationContext-hibernate.xml",
"applicationContext-test.xml"
};
}
@Override
protected void onSetUpInTransaction() throws Exception {
super.onSetUpInTransaction();
jdbcTemplate.execute(
"INSERT INTO category (id, name) " +
"VALUES (10, 'Category 1')");
jdbcTemplate.execute(
"INSERT INTO category (id, name) " +
"VALUES (20, 'Category 2')");
jdbcTemplate.execute(
"INSERT INTO recipe (id, title, instructions, date, category_id) " +
"VALUES (1, 'Recipe 1', 'Test this recipe', '2006-09-10', 10)");
jdbcTemplate.execute(
"INSERT INTO recipe (id, title, instructions, date, category_id) " +
"VALUES (2, 'Recipe 2', 'Test this recipe too', '2006-09-11', 10)");
jdbcTemplate.execute(
"INSERT INTO recipe (id, title, instructions, date, category_id) " +
"VALUES (3, 'Recipe 3', 'Test this recipe three', '2006-09-12', 20)");
}
}
public void testDelete() throws Exception {
Recipe recipe = new Recipe();
recipe.setId(1L);
recipe.delete();
sessionFactory.getCurrentSession().flush();
assertEquals(0, jdbcTemplate.queryForInt("SELECT count(*) FROM Recipe WHERE id = 1"));
}
@Transient
protected Session getSession() {
return sessionFactory.getCurrentSession();
}
@Transient
protected Criteria getCriteria() {
return getSession().createCriteria(this.getClass());
}
protected void expectGetCurrentSessionCall() {
sessionFactory.expects(once()).method("getCurrentSession")
.withNoArguments().will(new CustomStub("Session") {
public Object invoke(Invocation invocation) throws Throwable {
return (Session) session.proxy();
}
});
}
applicationContext = new StaticApplicationContext();
// It's a bit tricky to register a factory bean with a custom method name...
BeanDefinition aspectConfigurerDefinition = BeanDefinitionBuilder.
rootBeanDefinition(AnnotationBeanConfigurerAspect.class).
setFactoryMethod("aspectOf").getBeanDefinition();
// Register the configuring aspect
applicationContext.registerBeanDefinition(
AnnotationBeanConfigurerAspect.class.getName(), aspectConfigurerDefinition);
// Register the mocked session factory
applicationContext.getBeanFactory().
registerSingleton("sessionFactory",sessionFactory.proxy());
// Don't forget to refresh!
applicationContext.refresh();
public ModelAndView create(HttpServletRequest request, HttpServletResponse response) {
Listcategories = new Category().findAll();
return new ModelAndView("recipe/edit", "categories", categories);
}
public void testCreate() throws Exception {
// This is a utility method to prepare mock request/response
prepareRequest("/recipie/create.htm", "GET");
expectGetCriteriaCall(Category.class);
List toReturn = new ArrayList();
toReturn.add(new Category());
toReturn.add(new Category());
criteria.expects(once()).
method("list").
withNoArguments().
will(returnValue(toReturn));
ModelAndView mav = controller.handleRequest(request, response);
assertEquals("recipie/edit", mav.getViewName());
List result = (List) mav.getModel().get("categories");
assertEquals(2, result.size());
}
new Category().findAll()
@Entity
@Configurable(autowire=Autowire.BY_NAME)
public class Category extends BasicEntity<Category> {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class CategoryController extends MultiActionController {
public ModelAndView list(HttpServletRequest request, HttpServletResponse response) throws Exception {
List<Category> categories = new Category().findAll();
return new ModelAndView("category/list", "categories", categories);
}
...
// + create, edit and delete
}
@ManyToOne
@JoinColumn
public Category getCategory() {
return category;
}
@OneToMany(mappedBy="category")
public SetgetRecipes() {
return recipes;
}
<h4>Category</h4>
<select name="category">
<#list categories as c>
<option value="${c.id}"
<#if (recipe.category)?exists && (recipe.category.id == c.id)>selected="selected"</#if>>
${c.name}
</option>
</#list>
</select>
List<Category> categories = new Category().findAll();
...
model.put("categories", categories);
public void setAsText(String text) throws IllegalArgumentException {
Long id = Long.valueOf(text);
Category category = new Category();
category.setId(id);
setValue(category.load());
}
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder)
throws Exception {
binder.registerCustomEditor(Category.class, new CategoryPropertyEditor());
}
public ModelAndView store(HttpServletRequest request, HttpServletResponse response, Recipie recipie)
throws Exception {
if (recipie.getDate() == null) {
recipie.setDate(Calendar.getInstance());
}
recipie.store();
return new ModelAndView("redirect:list.htm");
}
<bean id="freeMarkerConfigurer" class="org.springframework...FreeMarkerConfigurer">
<property name="configLocation" value="/WEB-INF/classes/freemarker.properties" />
<property name="templateLoaderPath" value="/WEB-INF/freemarker" />
</bean>
<bean id="viewResolver" class="org.springframework...FreeMarkerViewResolver">
<property name="prefix" value="" />
<property name="suffix" value=".ftl" />
<property name="exposeSpringMacroHelpers" value="true" />
<property name="requestContextAttribute" value="rc" />
<property name="contentType" value="text/html; charset=UTF-8" />
</bean>
<bean id="openSessionInViewInterceptor" class="org.springframework...OpenSessionInViewInterceptor" />
<bean class="org.springframework...ControllerClassNameHandlerMapping">
<property name="interceptors" ref="openSessionInViewInterceptor">
</property>
<bean id="recipieController" class="se.petrix.cookbook.controller.RecipeController">
<#list recipies?if_exists as r>
<tr>
<td>#{r.id}</td>
<td>${r.title?if_exists}</td>
<td>${r.description?if_exists}</td>
<td>${r.instructions?if_exists}</td>
<td>${(r.date.time)?if_exists?date}</td>
<td>${(r.category.name)?if_exists}</td>
<td><a href="${rc.contextPath}/recipie/edit.htm?id=#{r.id}">Edit</a></td>
<td><a href="${rc.contextPath}/recipie/delete.htm?id=#{r.id}">Delete</a></td>
</tr>
</#list>
mvn war:inplace
export MAVEN_OPTS=-javaagent:src/main/webapp/WEB-INF/lib/aspectjweaver-1.5.2a.jar
mvn jetty:run
hibernate.hbm2ddl.auto=update
hibernate.show_sql=true
hibernate.format_sql=true
@Entity
@Configurable(autowire=Autowire.BY_NAME)
public class Recipe {
String title;
...
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
...
}
There are many interesting things here: the @MappedSuperclass annotation allows us to use this as a base class for mapped entities, with the Id property being mapped in subclasses. We're using generics to make return values match the inherited class, a varargs parameter for relational properties to join (relations are lazy by default in Hibernate 3), and we're using the @Transactional annotation to specify that the load() method should be run in a transaction.
@MappedSuperclass
public abstract class BasicEntityimplements Serializable {
private Long id;
protected SessionFactory sessionFactory;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Transient
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Transient
protected Session getSession() {
return sessionFactory.getCurrentSession();
}
@Transient
protected Criteria getCriteria() {
return getSession().createCriteria(this.getClass());
}
@SuppressWarnings("unchecked")
@Transactional(readOnly=true)
public T load() {
getSession().refresh(this);
return (T) this;
}
...
// + methods for store, delete and findAll
}
In other words, we need a transaction manager (since @Transactional isn't tied to any particular transaction implementation), a SessionFactory of course, and finally the two AspectJ aspects that are applied to classes and methods with the @Configurable and @Transactional annotations. All of this goes into applicationContext-hibernate.xml. For the AnnotationBeanConfigurerAspect bean, we could use the shorter form:
<bean id="transactionManager" class="org.springframework...HibernateTransactionManager">
<bean class="org.springframework...AnnotationBeanConfigurerAspect" method="aspectOf">
<bean class="org.springframework...AnnotationTransactionAspect" method="aspectOf">
<bean id="sessionFactory" class="org.springframework...AnnotationSessionFactoryBean">
<property name="annotatedClasses">
<list>
<value>se.petrix.cookbook.model.Recipe</value>
</list>
</property>
</bean>
<aop:spring-configured />
public class Recipe extends BasicEntity<Recipe> implements Serializable {
create database 'cookbook' character set 'utf8';
grant all on cookbook.* to 'cookbook'@'localhost' identified by 'cookbook';
mvn archetype:create -DgroupId=se.petrix -DartifactId=cookbook
mkdir -p src/main/resources
mkdir -p src/test/resources
mkdir -p src/main/webapp/WEB-INF
mvn eclipse:eclipse