1. 程式人生 > >Spring Boot 學習之持久層篇(三)

Spring Boot 學習之持久層篇(三)

該系列並非完全原創,官方文件作者

一、前言

上一篇《Spring Boot 入門之 Web 篇(二)》介紹了 Spring Boot 的 Web 開發相關的內容,專案的開發離不開資料,因此本篇開始介紹持久層相關的知識。

二、整合 JdbcTemplate

1、新增依賴

在pom.xml檔案中新增

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mysql 驅動包 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

2、配置資料庫連線

在 application-local.properties 中新增:

(DataSourceProperties.class,DataSourceAutoConfiguration.class原始碼)

spring.datasource.url=jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root

3、建表

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `password` varchar(64) NOT NULL,
  `createTime` date NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

4、實體序列化

上篇的User implements Serializable,程式碼就不貼了

5、Service、Dao

package com.phil.springboot.dao;

import com.phil.springboot.bean.User;

public interface UserDao {

	public int insert(User user);

	public int deleteById(Integer id);

	public int update(User user);

	public User getById(Integer id);
}
package com.phil.springboot.dao.imp;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import com.phil.springboot.bean.User;
import com.phil.springboot.dao.UserDao;

@Repository
public class UserDaoImpl implements UserDao {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Override
	public int insert(User user) {
		String sql = "insert into user(id,username,password,createTime) values(?,?,?,?)";
		return jdbcTemplate.update(sql, user.getId(), user.getUsername(), user.getPassword(), user.getCreateTime());
	}

	@Override
	public int deleteById(Integer id) {
		String sql = "delete from user where id = ?";
		return jdbcTemplate.update(sql, id);
	}

	@Override
	public int update(User user) {
		String sql = "update user set password = ? where id = ?";
		return jdbcTemplate.update(sql, user.getPassword(), user.getId());
	}

	@Override
	public User getById(Integer id) {
		String sql = "select * from user where id = ?";
		return jdbcTemplate.queryForObject(sql, new RowMapper<User>() {
			@Override
			public User mapRow(ResultSet rs, int rowNum) throws SQLException {
				User user = new User();
				user.setId(rs.getInt("id"));
				user.setUsername(rs.getString("username"));
				user.setPassword(rs.getString("password"));
				user.setCreateTime(rs.getDate("createTime"));
				return user;
			}

		}, id);
	}
}
package com.phil.springboot.service;

import com.phil.springboot.bean.User;

public interface UserService {

	public int insert(User user);

	public int deleteById(Integer id);

	public int update(User user);

	public User getById(Integer id);

}
package com.phil.springboot.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.phil.springboot.bean.User;
import com.phil.springboot.dao.UserDao;
import com.phil.springboot.service.UserService;

@Repository
public class UserServiceImpl implements UserService {
	
	@Autowired
	private UserDao userDao;

	@Override
	public int insert(User user) {
		return userDao.insert(user);
	}

	@Override
	public int deleteById(Integer id) {
		return userDao.deleteById(id);
	}

	@Override
	public int update(User user) {
		return userDao.update(user);
	}

	@Override
	public User getById(Integer id) {
		return userDao.getById(id);
	}
}

之前在架構師封裝方法上重構了部分,這裡就不貼了。

6、單元測試

package com.phil.springboot.user;

import java.util.Date;
import java.util.UUID;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.phil.springboot.bean.User;
import com.phil.springboot.dao.UserDao;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserTest {

	@Autowired
	private UserDao userDao;

	@Test
	public void testInsert() {
		User user = new User();
		user.setId(1);
		user.setUsername(UUID.randomUUID().toString().replace("-", "").substring(0, 4));
		user.setPassword(UUID.randomUUID().toString().replace("-", "").substring(0, 8));
		user.setCreateTime(new Date());
		int result = userDao.insert(user);
		System.out.println(result);
	}

	@Test
	public void testGetById() {
		User user = userDao.getById(1);
		System.out.println(user.getUsername());
	}

	@Test
	public void testUpdate() {
		User user = new User();
		user.setId(1);
		user.setPassword(UUID.randomUUID().toString().replace("-", "").substring(0, 8));
		userDao.update(user);
	}

	@Test
	public void testDeleteById() {
		int result = userDao.deleteById(1);
		System.out.println(result);
	}
}

如需列印日誌,在日誌配置檔案中新增如下配置:

<logger name="org.springframework.jdbc.core.JdbcTemplate" level="debug"/>

三、整合 Spring-data-jpa

1、新增依賴

在pom.xml檔案中新增

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

2、新增配置

在application-local.properties中新增

# JPA 配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

3、建立對映實體類

package com.phil.springboot.bean;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "PHIL_EMPLOYEE")
public class Employee implements Serializable {

	private static final long serialVersionUID = 3926276668667517847L;
	
	@Id
	@GeneratedValue
	private Integer id;

	@Column
	private String name;

	@Column
	private int age;

	@Column
	private String tile;

	public Employee() {
		super();
	}

	public Employee(Integer id) {
		super();
		this.id = id;
	}

	public Employee(Integer id, String name, int age, String tile) {
		super();
		this.id = id;
		this.name = name;
		this.age = age;
		this.tile = tile;
	}

	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 int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getTile() {
		return tile;
	}

	public void setTile(String tile) {
		this.tile = tile;
	}
}

4、繼承JpaRepository介面

package com.phil.springboot.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.phil.springboot.bean.Employee;

public interface EmployeeRepository extends JpaRepository<Employee, Integer>{

}

5、單元測試

package com.phil.springboot.employee;

import java.util.UUID;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.phil.springboot.bean.Employee;
import com.phil.springboot.repository.EmployeeRepository;

@RunWith(SpringRunner.class)
@SpringBootTest
public class EmployeeTest {

	@Autowired
	private EmployeeRepository employeeRepository;

	@Test
	public void testInsert() {
		Employee employee = new Employee();
		employee.setAge(25);
		employee.setName(UUID.randomUUID().toString().replace("-", "").substring(0, 4));
		employee.setTile("經理");
		Assert.assertNotNull(employeeRepository.save(employee));
	}

	@Test
	public void testFindOne() {
		Employee employee = employeeRepository.getOne(1);
		// Employee param = new Employee();
		// param.setName("經理");
		// employeeRepository.findOne(Example.of(param));
		Assert.assertNotNull(employee);
	}

	@Test
	public void testUpdate() {
		Employee employee = new Employee();
		employee.setId(1);
		employee.setAge(26);
		employee.setTile("總經理");
		Assert.assertNotNull(employeeRepository.save(employee));
	}

	@Test
	public void testDelete() {
		employeeRepository.deleteById(1);
	}
}

四、整合 Mybatis(註解)

1、新增依賴

在pom.xml檔案中新增

<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>1.3.2</version>
</dependency>

2、配置Mybatis資料來源

資料來源在application-local.properties已經配置過,在application.properties檔案新增配置資訊

mybatis.type-aliases-package=com.phil.springboot.bean

3、增加Mybatis掃描註解

在springboot啟動類,增加Mybatis掃描註解

@SpringBootApplication
@MapperScan("com.phil.springboot.mappper")
public class SpringbootApplication

4、建立資料表對映類

SQL

DROP TABLE IF EXISTS `question`;
CREATE TABLE `question` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `number` int(11) DEFAULT NULL,
  `description` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
package com.phil.springboot.bean;

public class Question {

	private Integer id;

	private Integer number;

	private String description;

	public Question() {
		super();
	}

	public Question(Integer id, Integer number, String description) {
		super();
		this.id = id;
		this.number = number;
		this.description = description;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public Integer getNumber() {
		return number;
	}

	public void setNumber(Integer number) {
		this.number = number;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}
}

5、建立Mapper資料持久層操作方法

package com.phil.springboot.mappper;

import java.util.List;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import com.phil.springboot.bean.Question;

public interface QuestionMapper {

	@Insert("INSERT INTO QUESTION(number,description) VALUES(#{number}, #{description})")
	void addQuestion(Question question);

	@Delete("DELETE FROM QUESTION WHERE id=#{id}")
	void deleteQuestion(Integer id);

	@Update("UPDATE QUESTION SET description=#{description} WHERE id=#{id}")
	void updateQuestion(Question question);

	@Select("SELECT * FROM QUESTION")
	@Results({ @Result(property = "number", column = "number"), @Result(property = "description", column = "description") })
	List<Question> queryQuestions();

	@Select("SELECT * FROM QUESTION WHERE number=#{number}")
	@Results({ @Result(property = "number", column = "number"), @Result(property = "description", column = "description") })
	Question queryQuestionByNumber(Integer number);
}

6、單元測試

package com.phil.springboot.question;

import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import com.phil.springboot.bean.Question;
import com.phil.springboot.mappper.QuestionMapper;

@RunWith(SpringRunner.class)
@SpringBootTest
public class QuestionTest {
	
	@Autowired
	private QuestionMapper questionMapper;

	@Test 
	public void testAddQuestion() {
		Question question = new Question();
		question.setNumber(1);
		question.setDescription("問題一描述");
		questionMapper.addQuestion(question);
		
		Question question_ = new Question();
		question_.setNumber(2);
		question_.setDescription("問題二描述");
		questionMapper.addQuestion(question_);
	}

	@Test
	public void testQuestionByNumber() {
		Question question = questionMapper.queryQuestionByNumber(1);
		System.out.println(question);
	}

	@Test
	public void testQueryAllQuestions() {
		List<Question> questions = questionMapper.queryQuestions();
		System.out.println(questions.toArray());
	}
	
	@Test
	public void testUpdateQuestion(){
		Question question = questionMapper.queryQuestionByNumber(1);
		question.setDescription("問題一不需要描述了");
		questionMapper.updateQuestion(question);
	}
	
	@Test
	public void testDelQuestion() {
		questionMapper.deleteQuestion(1);
	}
}

五、整合 Mybatis(配置)

和上節比較

1、新增依賴

不需要改動

2、配置Mybatis資料來源

在application.properties檔案再新增配置資訊

mybatis.config-location=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

3、增加Mybatis掃描註解

不需要改動

4、建立資料表對映類

不需要改動

5、建立Mapper資料持久層操作方法

修改如下

package com.phil.springboot.mappper;

import java.util.List;
import java.util.Map;

public interface QuestionMapper {

	public List<Map<String, Object>> findByPage(Map<String, Object> params);
	
	public Map<String, Object> findByProperty(Map<String, Object> params);

	public Integer save(Map<String, Object> params);

	public Integer update(Map<String, Object> params);

	public Integer delete(Integer[] ids );

}

6、增加Mybatis主配置檔案

mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
</configuration>

questionMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.phil.springboot.mappper.QuestionMapper" >
    <sql id="query" >
        select
        id, number, description
        from question 
    </sql>
    
    <select id="findByPage" parameterType="java.util.Map" resultType="java.util.Map">
    	<include refid="query" />
    	<where>
		   	<if test="id != null">
		   		id = #{id}
		   	</if>
    		<if test="number != null">
		   		and number = #{number}
		   	</if>
		   	<if test="description != null">
		   		and description like '%${description}%'
		   	</if>
    	</where>
    </select>
    
    <select id="findByProperty" parameterType="java.util.Map" resultType="java.util.Map">
    	<include refid="query" />
    	<where>
		   	<if test="id != null">
		   		id = #{id}
		   	</if>
    		<if test="number != null">
		   		and number = #{number}
		   	</if>
		   	<if test="description != null">
		   		and description like '%${description}%'
		   	</if>
    	</where>
    </select>
    
    <insert id="save" parameterType="java.util.Map" useGeneratedKeys="true" keyProperty="id">
         insert into
         question
         (number,description)
         values
         (#{number}, #{description})
    </insert>

    <update id="update" parameterType="java.util.Map">
        update
        question
        set
        number = #{number},
        description = #{description}
        where
        id = #{id}
    </update>

    <delete id="delete" parameterType="java.util.ArrayList" >
        delete from
        question
        where
        id in
        <foreach item="id" collection="array" open="(" separator="," close=")">
        	#{id}
        </foreach> 
    </delete>
</mapper>

7、單元測試

package com.phil.springboot.question;

import java.util.HashMap;
import java.util.Map;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.phil.springboot.mappper.QuestionMapper;

@RunWith(SpringRunner.class)
@SpringBootTest
public class QuestionMybtisTest {
	
	@Autowired
	private QuestionMapper questionMapper;

	@Test
	public void testInsert() {
		Map<String, Object> params_ = new HashMap<>();
		params_.put("number", 3);
		params_.put("description", "問題三描述\"");
		Assert.assertNotNull(questionMapper.update(params_));
	}

	@Test
	public void testGet() {
		Map<String, Object> params_ = new HashMap<>();
		params_.put("id", 3);
		Assert.assertNotNull(questionMapper.findByProperty(params_));
	}

	@Test
	public void testList() {
		Assert.assertNotNull(questionMapper.findByPage(new HashMap<>()));
	}
	
	@Test
	public void testUpdate(){
		Map<String, Object> params_ = new HashMap<>();
		params_.put("id", 5);
		params_.put("number", 5);
		params_.put("description", "新的問題5描述");
		Assert.assertNotNull(questionMapper.update(params_));
	}
	
	@Test
	public void testDelete () {
		Integer[] ids = new Integer[] {3,5};
		Assert.assertNotNull(questionMapper.delete(ids));
	}
}

Service沒貼,別忘了加事務註解

六、配置C3P0資料來源

1、新增依賴

在pom.xml檔案中新增

<!-- c3p0 -->
<dependency>
	<groupId>com.mchange</groupId>
	<artifactId>c3p0</artifactId>
	<version>0.9.5.2</version>
</dependency>

2、配置類

package com.phil.springboot.config;

import java.beans.PropertyVetoException;
import java.io.IOException;

import javax.sql.DataSource;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import com.mchange.v2.c3p0.ComboPooledDataSource;

@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class C3P0DataSourceConfig {

	@Value("${mybatis.mapper-locations}")
	private String mapperXMLConfigPath;
	
	@Value("${mybatis.type-aliases-package}")
	private String mapperPackagePath;
	
	@Autowired
	private DataSourceProperties dataSourceProperties;

	@Bean(name = "dataSource")
	@Qualifier(value = "dataSource") // 限定描述符除了能根據名字進行注入,但能進行更細粒度的控制如何選擇候選者
	@Primary // 用@Primary區分主資料來源
	public DataSource createDataSource() throws PropertyVetoException {
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
//		dataSource.setDriverClass(dataSourceProperties.getDriverClassName());
		dataSource.setJdbcUrl(dataSourceProperties.getUrl());
		dataSource.setUser(dataSourceProperties.getUsername());
		dataSource.setPassword(dataSourceProperties.getPassword());
		// 關閉連線後不自動提交
		dataSource.setAutoCommitOnClose(false);
		return dataSource;
//		return DataSourceBuilder.create().type(ComboPooledDataSource.class).build();//建立資料來源
	}

	/**
	 * 返回sqlSessionFactory
	 * @throws IOException 
	 * @throws PropertyVetoException 
	 */
	@Bean
	public SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException, PropertyVetoException {
		SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
		PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
		String packageXMLConfigPath = mapperXMLConfigPath; //PathMatchingResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
		// 設定mapper 對應的XML 檔案的路徑
		sqlSessionFactoryBean.setMapperLocations(resolver.getResources(packageXMLConfigPath));
		// 設定資料來源
		sqlSessionFactoryBean.setDataSource(createDataSource());
		// 設定mapper 介面所在的包
		sqlSessionFactoryBean.setTypeAliasesPackage(mapperPackagePath);
		return sqlSessionFactoryBean;
	}
}

控制檯

2018-04-04 16:06:39.671 |-INFO  [MLog-Init-Reporter] com.mchange.v2.log.MLog [212] -| MLog clients using slf4j logging.
2018-04-04 16:06:39.708 |-INFO  [restartedMain] com.mchange.v2.c3p0.C3P0Registry [212] -| Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
2018-04-04 16:06:39.955 |-INFO  [restartedMain] com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource [212] -| Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 2s4xuz9u13vbbdda00wcf|5f55253e, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> null, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 2s4xuz9u13vbbdda00wcf|5f55253e, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf8, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]

總結:

註解版更符合springboot微服務設計思想,快速、簡潔、零配置的開發模式。

如果習慣了在配置檔案中編寫sql(特別是複雜的sql),那麼可以選用配置版。(我喜歡這種)