using-lishys-notes.notes
Guide
The guide pane on the right contains information organized into sections.
Use the

and

buttons to move through the sections, or click an accordion tab (shown below) to go directly to a particular topic.

Code Reference
Each paragraph in the guide is usually associated with a particular piece of code. An orange arrow indicates that the code displayed in the middle pane is relevant to the guide text.

If the arrow is gray, simply click anywhere on the paragraph and the relevant code will be displayed.

Code Highlighter
The highlight feature identifies areas of the code which relate to a word or term in the guide. Hover the mouse over any underlined word and the relevant code will be highlighted.
You can disable the highlighting feature at any time by clicking on the

button.
Don't forget to make the appropriate code page visible by clicking on any paragraph with a gray arrow.
HibernatePersonDao.java
package com.lishman.testing.data.hibernate;
import java.util.Collection;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.lishman.testing.data.PersonDao;
import com.lishman.testing.domain.Person;
@Repository
@Transactional
public class HibernatePersonDao implements PersonDao {
@Autowired
SessionFactory sessionFactory;
@SuppressWarnings("unchecked")
@Transactional(readOnly = true)
public Collection<Person> getAll() {
return sessionFactory
.getCurrentSession()
.getNamedQuery("Person.findAll")
.list();
}
@Transactional(readOnly = true)
public Person getById(int personId) {
return (Person) sessionFactory
.getCurrentSession()
.get(Person.class, personId);
}
@Transactional(readOnly = true)
public Person getByName(String personName) {
return (Person) sessionFactory
.getCurrentSession()
.getNamedQuery("Person.findByName")
.setParameter("person_name", personName)
.uniqueResult();
}
public Person save(Person person) {
Person savedPerson = (Person) sessionFactory.getCurrentSession().merge(person);
sessionFactory.getCurrentSession().flush();
return savedPerson;
}
public void delete(Person person) {
sessionFactory.getCurrentSession().delete(person);
sessionFactory.getCurrentSession().flush();
}
}
JpaPersonDao.java
package com.lishman.testing.data.jpa;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.lishman.testing.data.PersonDao;
import com.lishman.testing.domain.Person;
@Repository
@Transactional
public class JpaPersonDao implements PersonDao {
@Autowired
JpaTemplate jpaTemplate;
@SuppressWarnings("unchecked")
@Transactional(readOnly = true)
public Collection<Person> getAll() {
return jpaTemplate.findByNamedQuery("Person.findAll");
}
@Transactional(readOnly = true)
public Person getById(int personId) {
return (Person) jpaTemplate.find(Person.class, personId);
}
@SuppressWarnings("unchecked")
@Transactional(readOnly = true)
public Person getByName(String name) {
Map<String, String> params = Collections.singletonMap("person_name", name);
List<Person> persons = jpaTemplate.findByNamedQueryAndNamedParams("Person.findByName", params);
return persons.isEmpty() ? null : persons.get(0);
}
public Person save(Person person) {
Person savedPerson = jpaTemplate.merge(person);
jpaTemplate.flush();
return savedPerson;
}
public void delete(Person person) {
jpaTemplate.remove(getById(person.getId()));
jpaTemplate.flush();
}
}
PersonDao.java
package com.lishman.testing.data;
import java.util.Collection;
import com.lishman.testing.domain.Person;
public interface PersonDao {
public Collection<Person> getAll();
public Person getById(int personId);
public Person getByName(String personName);
public Person save(Person person);
public void delete(Person person);
}
Person.java
package com.lishman.testing.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;
@Entity
@Table(name = "PERSON")
@NamedQueries ({
@NamedQuery(name="Person.findAll", query="select p from Person p order by p.name"),
@NamedQuery(name="Person.findByName", query="select p from Person p where p.name = :person_name")
})
public class Person {
@Id
@SequenceGenerator(name = "PersonSequence", sequenceName = "PERSON_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PersonSequence")
@Column(name = "PRSN_ID")
private Integer id;
@Column(name = "PRSN_NAME")
@NotEmpty
private String name;
@Column(name = "PRSN_AGE")
@NotNull
@Range(min=10, max=100)
private Integer age;
public Person () {}
public Person (String name, Integer age) {
setName(name);
setAge(age);
}
public Person (int id, String name, Integer age) {
this(name, age);
setId(id);
}
public boolean isNew() {
return id == null;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
PersonValidator.java
package com.lishman.testing.domain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import com.lishman.testing.data.PersonDao;
@Component
public class PersonValidator {
@Autowired
private PersonDao personDao;
public void validate(Person person, Errors errors) {
if (!errors.hasFieldErrors("name")) {
Person existingPerson = personDao.getByName(person.getName());
if (existingPerson != null
&& (person.isNew() ||
!person.getId().equals(existingPerson.getId()))) {
errors.rejectValue("name", "validation.exists", "already exists");
}
}
}
}
MyService.java
package com.lishman.testing.service;
import java.util.Collection;
import com.lishman.testing.domain.Person;
public interface MyService {
public Person pickWinner();
public Collection<Person> getAllPersons();
public Person getPersonById(int personId);
public Person getPersonByName(String name);
public Person savePerson(Person person);
public void deletePerson(Person person);
}
MyServiceImpl.java
package com.lishman.testing.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.lishman.testing.data.PersonDao;
import com.lishman.testing.domain.Person;
@Service
public class MyServiceImpl implements MyService {
@Autowired
private PersonDao personDao;
@Override
public Person pickWinner() {
List<Person> persons = new ArrayList<Person>(personDao.getAll());
if (persons.size() == 0) {
return null;
}
int randomInt = new Random().nextInt(persons.size()) ;
Person winner = persons.get(randomInt);
personDao.delete(winner);
return winner;
}
@Override
public Collection<Person> getAllPersons() {
return personDao.getAll();
}
@Override
public Person getPersonById(int personId) {
return personDao.getById(personId);
}
@Override
public Person getPersonByName(String name) {
return personDao.getByName(name);
}
@Override
public Person savePerson(Person person) {
return personDao.save(person);
}
@Override
public void deletePerson(Person person) {
personDao.delete(person);
}
}
PersonController.java
package com.lishman.testing.web;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.lishman.testing.domain.Person;
import com.lishman.testing.service.MyService;
@Controller
public class PersonController {
@Autowired
private MyService myService;
@RequestMapping("/person-list")
@ModelAttribute("persons")
public Collection<Person> getPersonList(HttpServletRequest request) {
request.setAttribute("my_attribute", "my_value");
return myService.getAllPersons();
}
@RequestMapping("/person-details/{id}")
public ModelAndView getPerson(@PathVariable("id") int personId) {
Person person = myService.getPersonById(personId);
return new ModelAndView("person-details", "person", person);
}
}
PersonForm.java
package com.lishman.testing.web;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import com.lishman.testing.domain.Person;
import com.lishman.testing.domain.PersonValidator;
import com.lishman.testing.service.MyService;
@Controller
@RequestMapping("/person-form")
@SessionAttributes("person")
public class PersonForm {
@Autowired
private PersonValidator personValidator;
@Autowired
private MyService myService;
@RequestMapping(method = RequestMethod.GET)
public Person setUpForm(@RequestParam(value = "id", required = false) Integer id) {
return id == null ? new Person() : myService.getPersonById(id);
}
@RequestMapping(params = "save", method = RequestMethod.POST)
public String save(@Valid Person person,
BindingResult result,
SessionStatus status) {
personValidator.validate(person, result);
if (result.hasErrors()) {
return "person-form";
} else {
myService.savePerson(person);
status.setComplete();
return "redirect:person-list";
}
}
@RequestMapping(params = "delete", method = RequestMethod.POST)
public String delete(Model model, SessionStatus status) {
Person person = (Person) model.asMap().get("person");
myService.deletePerson(person);
status.setComplete();
return "redirect:person-list";
}
}
jpa-persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence 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_1_0.xsd"
version="1.0">
<persistence-unit name="testing" transaction-type="RESOURCE_LOCAL"/>
</persistence>
HibernatePersonDaoTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:component-scan base-package="com.lishman.testing.data.hibernate"/>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
p:dataSource-ref="dataSource"
p:configurationClass="org.hibernate.cfg.AnnotationConfiguration"
p:packagesToScan="com.lishman.testing.domain">
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
</props>
</property>
<property name="eventListeners">
<map>
<entry key="merge">
<bean class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener"/>
</entry>
</map>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory"/>
</beans>
HibernatePersonDaoTest.java
package com.lishman.testing.data.hibernate;
import org.springframework.test.context.ContextConfiguration;
import com.lishman.testing.data.PersonDaoTest;
@ContextConfiguration
public class HibernatePersonDaoTest extends PersonDaoTest {
}
JpaPersonDaoTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:component-scan base-package="com.lishman.testing.data.jpa"/>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:persistenceUnitName="testing"
p:dataSource-ref="dataSource"
p:persistenceXmlLocation="classpath:META-INF/jpa-persistence.xml">
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
p:databasePlatform="${hibernate.dialect}"
p:showSql="${hibernate.show_sql}"/>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"/>
<bean id="jpaTemplate"
class="org.springframework.orm.jpa.JpaTemplate"
p:entityManagerFactory-ref="entityManagerFactory"/>
</beans>
JpaPersonDaoTest.java
package com.lishman.testing.data.jpa;
import org.springframework.test.context.ContextConfiguration;
import com.lishman.testing.data.PersonDaoTest;
@ContextConfiguration
public class JpaPersonDaoTest extends PersonDaoTest {
}
PersonDaoStub.java
package com.lishman.testing.data.stub;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
import com.lishman.testing.data.PersonDao;
import com.lishman.testing.domain.Person;
@Component
public class PersonDaoStub implements PersonDao {
private Map<Integer, Person> persons = new HashMap<Integer, Person>();
private int lastId;
public PersonDaoStub() {
persons.put(1, new Person(1, "First Person", 10));
persons.put(2, new Person(2, "Second Person", 20));
persons.put(3, new Person(3, "Third Person", 30));
persons.put(4, new Person(4, "Fourth Person", 40));
lastId = 4;
}
@Override
public Collection<Person> getAll() {
return persons.values();
}
@Override
public Person getById(int personId) {
return getPerson(personId);
}
@Override
public Person getByName(String name) {
for (Person person : persons.values()) {
if (person.getName().equals(name)) {
return getPerson(person.getId());
}
}
return null;
}
@Override
public Person save(Person person) {
if (person.isNew()) {
person.setId(++lastId);
}
persons.put(person.getId(), person);
return person;
}
@Override
public void delete(Person person) {
persons.remove(person.getId());
}
public Map<Integer, Person> getPersonMap() {
return Collections.unmodifiableMap(persons);
}
public int getLastId() {
return lastId;
}
private Person getPerson(int personId) {
if (!persons.containsKey(personId)) {
return null;
}
Person p = persons.get(personId);
return new Person(p.getId(), p.getName(), p.getAge());
}
}
PersonDaoTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>
</beans>
PersonDaoTest.java
package com.lishman.testing.data;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import com.lishman.testing.data.PersonDao;
import com.lishman.testing.domain.Person;
@ContextConfiguration
public class PersonDaoTest extends AbstractTransactionalJUnit4SpringContextTests {
@Autowired
private PersonDao personDao;
@Test
public void correctNumberOfRowsAreReturned() {
assertEquals(super.countRowsInTable("PERSON"), personDao.getAll().size());
}
@Test
public void correctPersonIsReturnedForSpecifiedId() {
Person person = personDao.getById(1);
assertEquals(new Integer(1), person.getId());
assertEquals("First Person", person.getName());
assertEquals((Integer) 10, person.getAge());
}
@Test
public void correctPersonIsReturnedForSpecifiedName() {
Person person = personDao.getByName("Second Person");
assertEquals(new Integer(2), person.getId());
assertEquals("Second Person", person.getName());
assertEquals((Integer) 20, person.getAge());
}
@Test
public void personIsCreated() {
Person person = personDao.save(new Person("New Name", 50));
Integer currId = super.simpleJdbcTemplate.queryForInt(
"SELECT person_seq.currval FROM dual");
currId *= 50; assertEquals(currId, person.getId());
Person newPerson = getPersonFromDatabase(currId);
assertEquals(currId, newPerson.getId());
assertEquals("New Name", newPerson.getName());
assertEquals((Integer) 50, newPerson.getAge());
}
@Test
public void personIsUpdated() {
Person person = personDao.getById(4);
assertEquals((Integer) 4, person.getId());
assertEquals("Fourth Person", person.getName());
assertEquals((Integer) 40, person.getAge());
person.setName("Updated Name");
person.setAge(60);
personDao.save(person);
Person newPerson = getPersonFromDatabase(4);
assertEquals((Integer) 4, newPerson.getId());
assertEquals("Updated Name", newPerson.getName());
assertEquals((Integer) 60, newPerson.getAge());
}
@Test
public void personIsDeleted() {
Person person = personDao.getById(3);
assertNotNull(person);
personDao.delete(person);
int rowCount = super.simpleJdbcTemplate.queryForInt(
"SELECT count(*) FROM person WHERE prsn_id = 3");
assertEquals(0, rowCount);
}
private Person getPersonFromDatabase(int currId) {
return super.simpleJdbcTemplate.queryForObject(
"SELECT prsn_id AS id, prsn_name AS name, prsn_age AS age " +
"FROM person " +
"WHERE prsn_id = ?",
new BeanPropertyRowMapper<Person>(Person.class),
currId);
}
}
DomainTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:annotation-driven/>
<context:component-scan base-package="com.lishman.testing.domain"/>
<context:component-scan base-package="com.lishman.testing.data.stub"/>
</beans>
PersonTest.java
package com.lishman.testing.domain;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.Iterator;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("DomainTest-context.xml")
public class PersonTest {
@Autowired
Validator validator;
@Test
public void personWithNullNameIsRejected() {
Person person = new Person(null, 10);
Set<ConstraintViolation<Person>> violations = validator.validate(person);
assertEquals(1, violations.size());
ConstraintViolation<Person> violation = violations.iterator().next();
assertEquals("name", violation.getPropertyPath().toString());
assertEquals("may not be empty", violation.getMessage().toString());
}
@Test
public void personWithNoNameIsRejected() {
Person person = new Person("", 10);
Set<ConstraintViolation<Person>> violations = validator.validate(person);
assertEquals(1, violations.size());
ConstraintViolation<Person> violation = violations.iterator().next();
assertEquals("name", violation.getPropertyPath().toString());
assertEquals("may not be empty", violation.getMessage().toString());
}
@Test
public void personWithNullAgeIsRejected() {
Person person = new Person("Name", null);
Set<ConstraintViolation<Person>> violations = validator.validate(person);
assertEquals(1, violations.size());
ConstraintViolation<Person> violation = violations.iterator().next();
assertEquals("age", violation.getPropertyPath().toString());
assertEquals("may not be null", violation.getMessage().toString());
}
@Test
public void personWithNoNameAndNoAgeIsRejected() {
Person person = new Person();
Set<ConstraintViolation<Person>> violations = validator.validate(person);
assertEquals(2, violations.size());
Iterator<ConstraintViolation<Person>> iter = violations.iterator();
String firstMessage = iter.next().getMessage().toString();
String secondMessage = iter.next().getMessage().toString();
if (firstMessage.equals("may not be null")) {
assertEquals("may not be empty", secondMessage);
} else if (firstMessage.equals("may not be empty")) {
assertEquals("may not be null", secondMessage);
} else {
fail("invalid first message");
}
}
@Test
public void personWhoIsTooYoungIsRejected() {
Person person = new Person("Name", 9);
Set<ConstraintViolation<Person>> violations = validator.validate(person);
assertEquals(1, violations.size());
ConstraintViolation<Person> violation = violations.iterator().next();
assertEquals("age", violation.getPropertyPath().toString());
assertEquals("must be between 10 and 100", violation.getMessage().toString());
}
@Test
public void personWhoIsTooOldIsRejected() {
Person person = new Person("Name", 101);
Set<ConstraintViolation<Person>> violations = validator.validate(person);
assertEquals(1, violations.size());
ConstraintViolation<Person> violation = violations.iterator().next();
assertEquals("age", violation.getPropertyPath().toString());
assertEquals("must be between 10 and 100", violation.getMessage().toString());
}
}
PersonValidatorTest.java
package com.lishman.testing.domain;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("DomainTest-context.xml")
public class PersonValidatorTest {
@Autowired
PersonValidator personValidator;
@Test
public void newPersonWithUniqueNameIsCreated() {
Person person = new Person("Unique Name", 80);
BindingResult result = new BeanPropertyBindingResult(person, "person");
personValidator.validate(person, result);
assertEquals(0, result.getAllErrors().size());
assertFalse(result.hasFieldErrors("name"));
}
@Test
public void newPersonWithDuplicateNameIsRejected() {
Person person = new Person("First Person", 25);
BindingResult result = new BeanPropertyBindingResult(person, "person");
personValidator.validate(person, result);
assertEquals(1, result.getAllErrors().size());
assertTrue(result.hasFieldErrors("name"));
assertEquals("already exists", result.getFieldError("name").getDefaultMessage());
}
@Test
public void existingPersonWithUniqueNameIsCreated() {
Person person = new Person(1, "Unique Name", 25);
BindingResult result = new BeanPropertyBindingResult(person, "person");
personValidator.validate(person, result);
assertEquals(0, result.getAllErrors().size());
assertFalse(result.hasFieldErrors("name"));
}
@Test
public void existingPersonWithDuplicateNameIsRejected() {
Person person = new Person(1, "Second Person", 25);
BindingResult result = new BeanPropertyBindingResult(person, "person");
personValidator.validate(person, result);
assertEquals(1, result.getAllErrors().size());
assertTrue(result.hasFieldErrors("name"));
assertEquals("already exists", result.getFieldError("name").getDefaultMessage());
}
}
MyServiceTest.java
package com.lishman.testing.service;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.annotation.Timed;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.lishman.testing.data.stub.PersonDaoStub;
import com.lishman.testing.domain.Person;
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({})
public class MyServiceTest {
private static MyService myStaticService;
private static PersonDaoStub personStaticDaoStub;
private MyService myService;
private PersonDaoStub personDaoStub;
@BeforeClass
public static void initStatic() {
personStaticDaoStub = new PersonDaoStub();
myStaticService = new MyServiceImpl();
ReflectionTestUtils.setField(myStaticService, "personDao", personStaticDaoStub);
}
@Before
public void init() {
personDaoStub = new PersonDaoStub();
myService = new MyServiceImpl();
ReflectionTestUtils.setField(myService, "personDao", personDaoStub);
}
@Test
@Repeat(4)
@Timed(millis=1000)
public void allWinnersArePickedWithinOneSecond() {
Person person = myStaticService.pickWinner();
assertNotNull(person);
}
@Test
public void allPersonsObjectsAreReturned() {
assertEquals(4, myService.getAllPersons().size());
}
@Test
public void personForSpecifiedIdIsReturned() {
Person person = myService.getPersonById(3);
assertEquals(new Integer(3), person.getId());
assertEquals("Third Person", person.getName());
assertEquals((Integer) 30, person.getAge());
}
@Test
public void personForSpecifiedNameIsReturned() {
Person person = myService.getPersonByName("Fourth Person");
assertEquals(new Integer(4), person.getId());
assertEquals("Fourth Person", person.getName());
assertEquals((Integer) 40, person.getAge());
}
@Test
public void personIsCreated() {
Person person = myService.savePerson(new Person("New Name", 60));
int id = person.getId();
assertEquals(new Integer(id), person.getId());
assertEquals("New Name", person.getName());
assertEquals((Integer) 60, person.getAge());
Person newPerson = personDaoStub.getPersonMap().get(id);
assertEquals(new Integer(id), newPerson.getId());
assertEquals("New Name", newPerson.getName());
assertEquals((Integer) 60, newPerson.getAge());
}
@Test
public void personIsUpdated() {
Person person = myService.getPersonById(3);
person.setName("Changed Name");
person.setAge(70);
myService.savePerson(person);
Person newPerson = personDaoStub.getPersonMap().get(3);
assertEquals((Integer) 3, newPerson.getId());
assertEquals("Changed Name", newPerson.getName());
assertEquals((Integer) 70, newPerson.getAge());
}
@Test
public void personIsDeleted() {
Person person = myService.getPersonById(3);
assertNotNull(person);
myService.deletePerson(person);
assertNull(personDaoStub.getPersonMap().get(3));
}
}
PersonControllerTest.java
package com.lishman.testing.web;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.ModelAndViewAssert;
import org.springframework.web.servlet.ModelAndView;
import com.lishman.testing.domain.Person;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("WebTest-context.xml")
public class PersonControllerTest {
@Autowired
private PersonController personController;
@Test
public void allPersonsAreReturned() {
MockHttpServletRequest request = new MockHttpServletRequest();
assertEquals(4, personController.getPersonList(request).size());
assertEquals("my_value", request.getAttribute("my_attribute"));
}
@Test
public void personForSpecifiedIdIsReturned() {
ModelAndView mav = personController.getPerson(3);
ModelAndViewAssert.assertViewName(mav, "person-details");
ModelAndViewAssert.assertModelAttributeAvailable(mav, "person");
Person tag = (Person) mav.getModelMap().get("person");
assertEquals(new Integer(3), tag.getId());
assertEquals("Third Person", tag.getName());
assertEquals((Integer) 30, tag.getAge());
}
}
PersonFormTest.java
package com.lishman.testing.web;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.support.SimpleSessionStatus;
import com.lishman.testing.data.stub.PersonDaoStub;
import com.lishman.testing.domain.Person;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("WebTest-context.xml")
public class PersonFormTest {
@Autowired
private PersonForm personForm;
@Autowired
private PersonDaoStub personDao;
@Test
public void emptyPersonObjectIsReturnedWhenNoIdIsSupplied() {
Person person = personForm.setUpForm(null);
assertNull(person.getId());
assertNull(person.getName());
assertNull(person.getAge());
}
@Test
public void populatedPersonObjectIsReturnedWhenIdIsSupplied() {
Person person = personForm.setUpForm(3);
assertEquals((Integer) 3, person.getId());
assertEquals("Third Person", person.getName());
assertEquals((Integer) 30, person.getAge());
}
@Test
@DirtiesContext
public void validPersonIsCreated() {
Person newPerson = new Person("New Name", 60);
BindingResult result = new BeanPropertyBindingResult(newPerson, "person");
SessionStatus status = new SimpleSessionStatus();
String viewName = personForm.save(newPerson, result, status);
assertEquals("redirect:person-list", viewName);
assertEquals(0, result.getAllErrors().size());
assertTrue(status.isComplete());
int idJustInserted = personDao.getLastId();
Person person = personDao.getPersonMap().get(idJustInserted);
assertEquals("New Name", person.getName());
assertEquals((Integer) 60, person.getAge());
assertEquals(5, personDao.getPersonMap().size());
}
@Test
@DirtiesContext
public void validPersonIsUpdated() {
Person newPerson = new Person(1, "Changed Name", 70);
BindingResult result = new BeanPropertyBindingResult(newPerson, "person");
SessionStatus status = new SimpleSessionStatus();
String viewName = personForm.save(newPerson, result, status);
assertEquals("redirect:person-list", viewName);
assertEquals(0, result.getAllErrors().size());
assertTrue(status.isComplete());
Person person = personDao.getPersonMap().get(1);
assertEquals("Changed Name", person.getName());
assertEquals((Integer) 70, person.getAge());
assertEquals(4, personDao.getPersonMap().size());
}
@Test
@DirtiesContext
public void personIsDeleted() {
Model model = new ExtendedModelMap();
model.addAttribute(new Person(2, "Second Person", 20));
SessionStatus status = new SimpleSessionStatus();
String viewName = personForm.delete(model, status);
assertEquals("redirect:person-list", viewName);
assertTrue(status.isComplete());
assertNull(personDao.getPersonMap().get(2));
assertEquals(3, personDao.getPersonMap().size());
}
@Test
public void newPersonIsRejectedIfAlreadyExists() {
Person newPerson = new Person("First Person", 10);
BindingResult result = new BeanPropertyBindingResult(newPerson, "person");
SessionStatus status = new SimpleSessionStatus();
assertEquals("person-form", personForm.save(newPerson, result, status));
assertEquals(1, result.getAllErrors().size());
assertTrue(result.hasFieldErrors("name"));
assertEquals("already exists", result.getFieldError("name").getDefaultMessage());
assertFalse(status.isComplete());
}
@Test
public void updatedPersonIsRejectedIfAlreadyExists() {
Person newPerson = new Person(1, "Second Person", 10);
BindingResult result = new BeanPropertyBindingResult(newPerson, "person");
SessionStatus status = new SimpleSessionStatus();
assertEquals("person-form", personForm.save(newPerson, result, status));
assertEquals(1, result.getAllErrors().size());
assertTrue(result.hasFieldErrors("name"));
assertEquals("already exists", result.getFieldError("name").getDefaultMessage());
assertFalse(status.isComplete());
}
}
WebTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="com.lishman.testing.web"/>
<context:component-scan base-package="com.lishman.testing.service"/>
<context:component-scan base-package="com.lishman.testing.domain"/>
<context:component-scan base-package="com.lishman.testing.data.stub"/>
</beans>
FullTestSuite.java
package com.lishman.testing;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import com.lishman.testing.data.hibernate.HibernatePersonDaoTest;
import com.lishman.testing.data.jpa.JpaPersonDaoTest;
import com.lishman.testing.domain.PersonTest;
import com.lishman.testing.domain.PersonValidatorTest;
import com.lishman.testing.service.MyServiceTest;
import com.lishman.testing.web.PersonControllerTest;
import com.lishman.testing.web.PersonFormTest;
@RunWith(Suite.class)
@SuiteClasses({
HibernatePersonDaoTest.class,
JpaPersonDaoTest.class,
PersonTest.class,
PersonValidatorTest.class,
MyServiceTest.class,
PersonControllerTest.class,
PersonFormTest.class,
})
public class FullTestSuite {
}
jdbc.properties
jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@my_test_server:1521:my_db
jdbc.username=my_user
jdbc.password=my_password
hibernate.generate_statistics=true
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
person-form.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>Person Details</title>
</head>
<body>
<h1>Person Details</h1>
<form:form modelAttribute="person" action="person-form" method="post">
<c:choose>
<c:when test="${person.new}">
<button type="submit" name="save">
create
</button>
</c:when>
<c:otherwise>
<button type="submit" name="save">
update
</button>
<button type="submit" name="delete" onclick="return confirm('Delete this person?')">
delete
</button>
</c:otherwise>
</c:choose>
<p>
Name<br>
<form:input path="name"><br/>
<form:errors path="name">
</p>
<p>
Age<br>
<form:input path="age"><br/>
<form:errors path="age">
</p>
</form:form>
<a href="<c:url value=" /app/person-list.html">" >
«cancel
</a>
</body>
</html>
person-list.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Person List</title>
</head>
<body>
<h1>Person List</h1>
<a href="<c:url value=" /app/person-form.html">">
<button>create</button>
</a>
<table>
<c:forEach items="${persons}" var="person">
<tr>
<td>
<a href="<c:url value='/app/person-form.html?id=${person.id}'/>">
${person.name}
</a>
</td>
</tr>
</c:forEach>
</table>
</body>
</html>
hibernate-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:component-scan base-package="com.lishman.testing.data.hibernate"/>
<tx:annotation-driven transaction-manager="txnManager"/>
<context:property-placeholder location="WEB-INF/spring/data/jdbc.properties"/>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"
p:maxActive="${dbcp.maxActive}"
p:maxIdle="${dbcp.maxIdle}"
p:maxWait="${dbcp.maxWait}"/>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
p:dataSource-ref="dataSource"
p:configurationClass="org.hibernate.cfg.AnnotationConfiguration"
p:packagesToScan="com.lishman.testing.domain">
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
</props>
</property>
<property name="eventListeners">
<map>
<entry key="merge">
<bean class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener"/>
</entry>
</map>
</property>
</bean>
<bean id="txnManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory"/>
</beans>
jdbc.properties
jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@my_server:1521:my_db
jdbc.username=my_user
jdbc.password=my_password
dbcp.maxActive=100
dbcp.maxIdle=30
dbcp.maxWait=20000
hibernate.generate_statistics=true
hibernate.show_sql=false
hibernate.format_sql=false
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
jpa-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:component-scan base-package="com.lishman.testing.data.jpa"/>
<context:property-placeholder location="WEB-INF/spring/data/jdbc.properties"/>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"
p:maxActive="${dbcp.maxActive}"
p:maxIdle="${dbcp.maxIdle}"
p:maxWait="${dbcp.maxWait}"/>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:persistenceUnitName="testing"
p:dataSource-ref="dataSource"
p:persistenceXmlLocation="classpath:META-INF/jpa-persistence.xml">
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
p:databasePlatform="${hibernate.dialect}"
p:showSql="${hibernate.show_sql}"/>
</property>
</bean>
<tx:annotation-driven transaction-manager="txnManager"/>
<bean id="txnManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="jpaTemplate" class="org.springframework.orm.jpa.JpaTemplate">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
</beans>
app-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="com.lishman.testing.service"/>
<context:component-scan base-package="com.lishman.testing.domain"/>
<import resource="mvc-config.xml"/>
<import resource="data/jpa-config.xml"/>
</beans>
mvc-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:annotation-driven/>
<context:component-scan base-package="com.lishman.testing.web"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp"/>
</beans>
log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file hibernate.log ###
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.logger=org.springframework=INFO
log4j.rootLogger=INFO, stdout
#log4j.logger.helloworld.web.HelloWorldController=info, stdout
#log4j.logger.org.hibernate=info
log4j.logger.org.hibernate=info
### log HQL query parser activity
#log4j.logger.org.hibernate.hql.ast.AST=debug
### log just the SQL
log4j.logger.org.hibernate.SQL=info
### log JDBC bind parameters ###
#log4j.logger.org.hibernate.type=info
log4j.logger.org.hibernate.type=info
### log schema export/update ###
log4j.logger.org.hibernate.tool.hbm2ddl=info
### log HQL parse trees
#log4j.logger.org.hibernate.hql=debug
### log cache activity ###
#log4j.logger.org.hibernate.cache=debug
### log transaction activity
#log4j.logger.org.hibernate.transaction=debug
### log JDBC resource acquisition
#log4j.logger.org.hibernate.jdbc=debug
### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace
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"
version="2.4"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/app-config.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>app/person-list</welcome-file>
</welcome-file-list>
</web-app>
schema.sql
DROP TABLE person;
DROP SEQUENCE person_seq;
CREATE TABLE person (
prsn_id NUMBER,
prsn_name VARCHAR2(100) NOT NULL,
prsn_age NUMBER NOT NULL,
CONSTRAINT person_pk PRIMARY KEY (prsn_id),
CONSTRAINT person_uk UNIQUE (prsn_name),
CONSTRAINT person_cc CHECK (prsn_age BETWEEN 10 AND 100)
);
CREATE SEQUENCE person_seq START WITH 100;
CREATE OR REPLACE TRIGGER person_trg
BEFORE INSERT ON person FOR EACH ROW
BEGIN
IF :NEW.prsn_id IS NULL THEN
SELECT person_seq.NEXTVAL INTO :NEW.prsn_id FROM DUAL;
END IF;
END;
/
unit-test-data.sql
TRUNCATE TABLE person;
INSERT INTO person (prsn_id, prsn_name, prsn_age) VALUES (1, 'First Person', 10);
INSERT INTO person (prsn_id, prsn_name, prsn_age) VALUES (2, 'Second Person', 20);
INSERT INTO person (prsn_id, prsn_name, prsn_age) VALUES (3, 'Third Person', 30);
INSERT INTO person (prsn_id, prsn_name, prsn_age) VALUES (4, 'Fourth Person', 40);
COMMIT;