Spring笔记07-Hibernate JPA与Spring Data

7.1 映射JPA实体

JPA: Java Persistence API 它是从EJB3中抽取出来的。

  • EntityManagerFactory
  • EntityManager
  • persistence.xml
  • @Entity
  • @Column
  • @Table

要使用JPA,我们需要添加依赖:

1
compile group: 'javax', name: 'javaee-api', version: '7.0'

我们来新建数据:

1
2
3
4
5
6
7
8
@Entity
public class Account {
@Id
private Long id;
private BigDecimal balance;

//...
}

然后配置数据源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
@ComponentScan(basePackages = "lx.spring.core")
@PropertySource("classpath:prod.properties")
@EnableTransactionManagement
public class AppConfig {

@Autowired
private Environment env;

@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getProperty("db.driver"));
dataSource.setUrl(env.getProperty("db.url"));
dataSource.setUsername(env.getProperty("db.user"));
dataSource.setPassword(env.getProperty("db.pass"));
return dataSource;
}
}

7.2 Entity Manager和Vendor Adapter

首先我们先添加依赖:

1
2
3
4
5
6
7
8
9
10
11
dependencies {
compile group: 'org.springframework', name: 'spring-jdbc', version: '4.3.2.RELEASE' //1. jdbc
compile group: 'org.springframework', name: 'spring-orm', version: '4.3.2.RELEASE' //2. orm
compile group: 'org.apache.commons', name: 'commons-dbcp2', version: '2.1.1' //3. dbcp
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.2.2.Final' //4. hibernate
//...

compile 'com.h2database:h2:1.4.192' //5. h2驱动
compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.3' //6. mysql驱动

}

现在我们来修改AppConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(true);
adapter.setGenerateDdl(true);
adapter.setDatabase(Database.MYSQL);
return adapter;
}

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory (
DataSource dataSource, JpaVendorAdapter jpaVendorAdapter
) {
Properties props = new Properties();
props.setProperty("hibernate.format_sql", String.valueOf(true));

LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource);
emf.setPackagesToScan("lx.spring.core.entities");
emf.setJpaVendorAdapter(jpaVendorAdapter);
emf.setJpaProperties(props);

return emf;
}

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}

@Bean
public BeanPostProcessor persistenceTranslation() {
return new PersistenceAnnotationBeanPostProcessor();
}

7.3 实现JPA Repository

现在我们用EntityManagerFactory来实现JPA Repository。

我们来创建AccountRepository接口和JpaAccountRepository类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package lx.spring.core.repositories;

import lx.spring.core.entities.Account;

import java.math.BigDecimal;
import java.util.List;

public interface AccountRepository {
List<Account> getAccounts();

Account getAccount(Long id);

int getNumberOfAccounts();

Long createAccount(BigDecimal initialBalance);

int deleteAccount(Long id);

void updateAccount(Account account);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package lx.spring.core.repositories;

import lx.spring.core.entities.Account;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.math.BigDecimal;
import java.util.List;

@Repository
public class JpaAccountRepository implements AccountRepository {
private long nextId = 4;

@PersistenceContext //Spring会注入
private EntityManager entityManager;

@Override
public List<Account> getAccounts() {
return entityManager.createQuery("select a from Account a", Account.class).getResultList();
}

@Override
public Account getAccount(Long id) {
return entityManager.find(Account.class, id);
}

@Override
public int getNumberOfAccounts() {
String jpaText = "select count(a.id) from Account a";
Long result = (Long) entityManager.createQuery(jpaText).getSingleResult();
return result.intValue();
}

@Override
public Long createAccount(BigDecimal initialBalance) {
long id = nextId++;
entityManager.persist(new Account(id, initialBalance));
return id;
}

@Override
public int deleteAccount(Long id) {
entityManager.remove(getAccount(id));
return 1;
}

@Override
public void updateAccount(Account account) {
entityManager.merge(account);
}
}

这里我们用的不是标准的SQL,而是JPA表达式。而且像select count(a.id) from Account a这些语句里都是类名和字段名,所以是区分大小写的。

7.4 测试

我们创建的测试和上一节类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=AppConfig.class)
@Transactional
public class JpaAccountRepositoryTest {
@Autowired
private AccountRepository repository;

@Test
public void getAccounts() throws Exception {
List<Account> accounts = repository.getAccounts();
assertThat(accounts.size(), is(3));
}

@Test
public void getNumberOfAccounts() throws Exception {
assertThat(repository.getNumberOfAccounts(), is(3));
}

@Test
public void getAccount() throws Exception {
Account account = repository.getAccount(1L);
assertThat(account.getId(), is(1L));
//2. 近似
assertThat(new BigDecimal("100.0"), is(closeTo(account.getBalance(), new BigDecimal("0.01"))));
}

@Test
public void createAccount() throws Exception {
Long id = repository.createAccount(new BigDecimal("250.00"));
//3. 非空
assertThat(id, is(notNullValue()));

Account account = repository.getAccount(id);
assertThat(account.getId(), is(id));
assertThat(new BigDecimal("250.0"), is(closeTo(account.getBalance(), new BigDecimal("0.01"))));
}

@Test
public void updateAccount() throws Exception {
Account account = repository.getAccount(1L);
BigDecimal current = account.getBalance();
BigDecimal amount = new BigDecimal("50.0");
account.setBalance(current.add(amount));
repository.updateAccount(account);

Account again = repository.getAccount(1L);
assertThat(again.getBalance(), is(closeTo(account.getBalance(), new BigDecimal("0.01"))));
}

@Test
public void deleteAccount() throws Exception {
for (Account account: repository.getAccounts()) {
repository.deleteAccount(account.getId());
}
assertThat(repository.getNumberOfAccounts(), is(0));
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
@Transactional
public class AccountServiceTest {

@Autowired
private AccountService service;

@Test
public void deposit() throws Exception {
BigDecimal start = service.getBalance(1L);
BigDecimal amount = new BigDecimal("50.0");

service.deposit(1L, amount);
BigDecimal finish = start.add(amount);

assertThat(finish, is(closeTo(service.getBalance(1L), new BigDecimal("0.01"))));
}

@Test
public void withdraw() throws Exception {
BigDecimal start = service.getBalance(1L);
BigDecimal amount = new BigDecimal("50.0");

service.withdraw(1L, amount);
BigDecimal finish = start.subtract(amount);

assertThat(finish, is(closeTo(service.getBalance(1L), new BigDecimal("0.01"))));
}

@Test
public void transfer() throws Exception {
BigDecimal acct1start = service.getBalance(1L);
BigDecimal acct2start = service.getBalance(2L);

BigDecimal amount = new BigDecimal("50.0");
service.transfer(1L, 2L, amount);
BigDecimal acct1finish = acct1start.subtract(amount);
BigDecimal acct2finish = acct2start.add(amount);

assertThat(acct1finish, is(closeTo(service.getBalance(1L), new BigDecimal("0.01"))));
assertThat(acct2finish, is(closeTo(service.getBalance(2L), new BigDecimal("0.01"))));
}
}

7.5 Spring Data JPA

Spring Data JPA能够进一步简化我们的应用。首先我们添加依赖:

1
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '1.10.2.RELEASE'

然后我们修改AppConfig.java

1
2
3
4
5
6
@Configuration
@ComponentScan(basePackages = "lx.spring.core")
@EnableJpaRepositories(basePackages = "lx.spring.core.repositories") //这个注解是用来发现spring data jpa的
@PropertySource("classpath:prod.properties")
@EnableTransactionManagement
public class AppConfig {...}

注意@EnableJpaRepositories注解。

接着,我们修改AccountRepository并删除对应的实现类:

1
2
3
public interface AccountRepository extends JpaRepository<Account, Long>{
List<Account> findAccountsByBalanceGreaterThanEqual(BigDecimal amount);
}

注意这里我们继承了JpaRepository,这样就可以了。不需要实现什么。

同时,我们定义了findAccountsByBalanceGreaterThanEqual方法,这个名称是约定的,find表示查找,By后面的是字段,GreaterThanEqual是条件。

此时,我们在测试中调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=AppConfig.class)
@Transactional
public class JpaAccountRepositoryTest {
@Autowired
private AccountRepository repository;

@Test
public void getAccounts() throws Exception {
List<Account> accounts = repository.findAll();
assertThat(accounts.size(), is(3));
}

@Test
public void getNumberOfAccounts() throws Exception {
assertThat(repository.count(), is(3));
}

@Test
public void getAccount() throws Exception {
Account account = repository.findOne(1L);
assertThat(account.getId(), is(1L));
//2. 近似
assertThat(new BigDecimal("100.0"), is(closeTo(account.getBalance(), new BigDecimal("0.01"))));
}

@Test
public void createAccount() throws Exception {
Account account = new Account(99L, new BigDecimal("250.00"));
repository.save(account);
Long id = account.getId();
//3. 非空
assertThat(id, is(notNullValue()));

Account again = repository.findOne(id);
assertThat(again.getId(), is(id));
assertThat(new BigDecimal("250.0"), is(closeTo(account.getBalance(), new BigDecimal("0.01"))));
}

@Test
public void updateAccount() throws Exception {
Account account = repository.findOne(1L);
BigDecimal current = account.getBalance();
BigDecimal amount = new BigDecimal("50.0");
account.setBalance(current.add(amount));
repository.save(account);

Account again = repository.findOne(1L);
assertThat(again.getBalance(), is(closeTo(account.getBalance(), new BigDecimal("0.01"))));
}

@Test
public void deleteAccount() throws Exception {
repository.deleteAll();
assertThat(repository.count(), is(0L));
}

}

可以看到只是方法名变了,怎么样,是不是很简单?