предисловие
Spring JDBC Templet — это базовая инкапсуляция использования JDBC в Spring. В основном он помогает программистам реализовать управление подключениями к базе данных, а в остальном использование мало чем отличается от непосредственного использования JDBC.
Потребности бизнеса
Все знакомы с использованием JDBC. Это в основном для демонстрации шагов использования Spring JDBC Templet в SpringBoot, поэтому мы разрабатываем простое требование. Операция CURD пользовательского объекта. У объекта есть два свойства: идентификатор и имя. Хранится в таблице MySQL auth_user.
Создайте новый проект и добавьте зависимости
Создайте новый пустой проект Scramboot в Intellij Idex. Обратитесь к конкретным шагамПервое знакомство со SpringBoot. Согласно требованиям этого примера нам нужно добавить следующие три зависимости
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
Поскольку мы хотим публиковать сервисы Http Rest, мы добавляем зависимость spring-boot-starter-web. Здесь мы хотим использовать метод JDBC Tempet для доступа к базе данных, поэтому мы добавляем зависимость spring-boot-starter-jdbc. Чтобы получить доступ к базе данных MySQL, мы добавляем последнюю версию драйвера JDBC для MySQL.
Подготовьте среду базы данных
Предполагается, что MySQL 5.7 был установлен в операционной системе Linux. Следующие операции выполняются в командной строке операционной системы и входят в клиент командной строки MySQL через пользователя root.
Создайте базу данных и создайте таблицу
create database springboot_jdbc;
create table auth_user (uuid bigint not null,name varchar(32), primary key (uuid)) default charset=utf8mb4;
Установить права пользователя
grant all privileges on springboot_jdbc.* to 'springboot'@'%' identified by 'springboot';
flush privileges;
Настроить источник данных (пул соединений)
Источник данных SpringBoot настраивается автоматически. В SpringBoot 2.0 есть несколько вариантов конфигурации источника данных, и они выбирают, какой источник данных фактически используется последним в соответствии с порядком приоритета HikariCP -> Tomcat pooling -> Commons DBCP2.
Когда проект добавил зависимость spring-boot-starter-jdbc, зависимость источника данных HikariCP уже была включена, поэтому здесь автоматически настраивается источник данных пула соединений HikariCP.
Добавьте следующую конфигурацию в apps.properties
#通用数据源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://10.110.2.5:3306/spring-boot-jdbc?charset=utf8mb4&useSSL=false
spring.datasource.username=springboot
spring.datasource.password=springboot
# Hikari 数据源专用配置
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
Большая часть конфигурации источника данных Hikari показана на рисунке ниже. Вы можете самостоятельно проверить значение каждой конфигурации
разработка программы
Участник пользовательской базы данных
Согласно требованиям, соответствующий объект пользовательских данных имеет два атрибута: один — id, другой — name. Это чистый объект POJO.
package com.yanggaochao.springboot.learn.springbootjdbclearn.domain.dao;
/**
* 用户实体对象
*
* @author 杨高超
* @since 2018-03-09
*/
public class UserDO {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Общий возвращаемый объект Http Rest
Обычно в интерфейсе Http Rest мы хотим не только напрямую возвращать содержимое бизнес-объекта, но также возвращать некоторую общую информацию, такую как результат вызова интерфейса, пользовательское текстовое сообщение, возвращаемое при сбое вызова, и т. д. . Затем нам нужно установить два общих объекта возврата остатка, в дополнение к возврату результатов вызовов общего интерфейса и текстовых сообщений, один содержит один бизнес-контент, а другой содержит коллекцию, которая содержит несколько бизнес-контентов. Конкретное определение выглядит следующим образом
Индивидуальный возвращаемый объект бизнес-контента
package com.yanggaochao.springboot.learn.springbootjdbclearn.domain.bo;
/**
* 单个对象返回结果
*
* @author 杨高超
* @since 2018-03-09
*/
public class RestItemResult<T> {
private String result;
private String message;
private T item;
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
Объект возврата бизнес-контента коллекции
package com.yanggaochao.springboot.learn.springbootjdbclearn.domain.bo;
import java.util.Collection;
/**
* 集合对象返回结果
*
* @author 杨高超
* @since 2018-03-09
*/
public class RestCollectionResult<T> {
private String result;
private String message;
private Collection<T> items;
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Collection<T> getItems() {
return items;
}
public void setItems(Collection<T> items) {
this.items = items;
}
}
Разработка уровня сохраняемости данных
Определение интерфейса уровня сохраняемости пользовательских данных
package com.yanggaochao.springboot.learn.springbootjdbclearn.dao;
import com.yanggaochao.springboot.learn.springbootjdbclearn.domain.dao.UserDO;
import java.util.List;
/**
* 用户数据层接口
*
* @author 杨高超
* @since 2018-03-09
*/
public interface UserDao {
/**
* 向数据库中保存一个新用户
*
* @param user 要保存的用户对象
* @return 是否增肌成功
*/
Boolean add(UserDO user);
/**
* 更新数据库中的一个用户
*
* @param user 要更新的用户对象
* @return 是否更新成功
*/
Boolean update(UserDO user);
/**
* 删除一个指定的用户
*
* @param id 要删除的用户的标识
* @return 是否删除成功
*/
boolean delete(Long id);
/**
* 精确查询一个指定的用户
*
* @param id 要查询的用户的标识
* @return 如果能够查询到,返回用户信息,否则返回 null
*/
UserDO locate(Long id);
/**
* 通过名称模糊查询用户
*
* @param name 要模糊查询的名称
* @return 查询到的用户列表
*/
List<UserDO> matchName(String name);
}
Реализация уровня сохраняемости пользовательских данных
package com.yanggaochao.springboot.learn.springbootjdbclearn.dao.impl;
import com.yanggaochao.springboot.learn.springbootjdbclearn.dao.UserDao;
import com.yanggaochao.springboot.learn.springbootjdbclearn.domain.dao.UserDO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
/**
* 用户对象数据库访问实现类
*
* @author 杨高超
* @since 2018-03-09
*/
@Repository
public class UserDaoJDBCTempletImpl implements UserDao {
private final JdbcTemplate jdbcTemplate;
@Autowired
public UserDaoJDBCTempletImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Boolean add(UserDO user) {
String sql = "INSERT INTO AUTH_USER(UUID,NAME) VALUES(?,?)";
return jdbcTemplate.update(sql, user.getId(), user.getName()) > 0;
}
@Override
public Boolean update(UserDO user) {
String sql = "UPDATE AUTH_USER SET NAME = ? WHERE UUID = ?";
return jdbcTemplate.update(sql, user.getName(), user.getId()) > 0;
}
@Override
public boolean delete(Long id) {
String sql = "DELETE FROM AUTH_USER WHERE UUID = ?";
return jdbcTemplate.update(sql, id) > 0;
}
@Override
public UserDO locate(Long id) {
String sql = "SELECT * FROM AUTH_USER WHERE UUID=?";
SqlRowSet rs = jdbcTemplate.queryForRowSet(sql, id);
if (rs.next()) {
return generateEntity(rs);
}
return null;
}
@Override
public List<UserDO> matchName(String name) {
String sql = "SELECT * FROM AUTH_USER WHERE NAME LIKE ?";
SqlRowSet rs = jdbcTemplate.queryForRowSet(sql, "%" + name + "%");
List<UserDO> users = new ArrayList<>();
while (rs.next()) {
users.add(generateEntity(rs));
}
return users;
}
private UserDO generateEntity(SqlRowSet rs) {
UserDO weChatPay = new UserDO();
weChatPay.setId(rs.getLong("UUID"));
weChatPay.setName(rs.getString("NAME"));
return weChatPay;
}
}
Здесь сначала используется аннотация @Repository, чтобы указать, что это класс уровня сохраняемости данных, и SpringBoot автоматически создаст экземпляр этого класса. Затем добавьте в конструктор @Autowired.Когда SpringBoot создаст экземпляр этого класса, он автоматически внедрит экземпляр JDBCTemlet в этот класс. Здесь экземпляр JDBCTemplet автоматически настраивается SpringBoot в соответствии с конфигурацией, связанной с источником данных, в файле application.properties. Согласно алгоритму SpringBoot для автоматической настройки источников данных, здесь будет настроен источник данных HikariCP.
В остальном это то же самое, что и обычная разработка Spring JDBCTemplet.Программист вручную конвертирует объект и базу данных SQL для реализации функций добавления, изменения, удаления, нечеткого сопоставления и точного запроса пользователей.
Разработка бизнес-уровня данных
Определение интерфейса бизнес-уровня данных
package com.yanggaochao.springboot.learn.springbootjdbclearn.service;
import com.yanggaochao.springboot.learn.springbootjdbclearn.domain.dao.UserDO;
import java.util.List;
/**
* 用户服务层接口
*
* @author 杨高超
* @since 2018-03-09
*/
public interface UserService {
UserDO add(UserDO user);
UserDO update(UserDO user);
boolean delete(Long id);
UserDO locate(Long id);
List<UserDO> matchName(String name);
}
Реализация бизнес-уровня данных
package com.yanggaochao.springboot.learn.springbootjdbclearn.service.impl;
import com.yanggaochao.springboot.learn.springbootjdbclearn.dao.UserDao;
import com.yanggaochao.springboot.learn.springbootjdbclearn.domain.dao.UserDO;
import com.yanggaochao.springboot.learn.springbootjdbclearn.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
/**
* 用户业务层实现类
*
* @author 杨高超
* @since 2018-03-09
*/
@Service
public class UserServiceImpl implements UserService {
private final UserDao userDao;
@Autowired
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public UserDO add(UserDO user) {
user.setId(new Date().getTime());
if (userDao.add(user)) {
return user;
}
return null;
}
@Override
public UserDO update(UserDO user) {
if (userDao.update(user)) {
return locate(user.getId());
}
return null;
}
@Override
public boolean delete(Long id) {
return userDao.delete(id);
}
@Override
public UserDO locate(Long id) {
return userDao.locate(id);
}
@Override
public List<UserDO> matchName(String name) {
return userDao.matchName(name);
}
}
Здесь класс реализации объявлен классом бизнес-слоя через аннотацию @Service. Когда USERDAO слоя настойчивости позволяет Sprilboot создавать класс бизнес-слоя через @auationired, он автоматически вводит соответствующий класс слоя настойчивости в бизнес класс.
Здесь при добавлении объекта пользователя, при установке идентификатора пользователя в качестве идентификатора просто используется миллисекунда текущего времени. В реальном процессе разработки это место должно использовать глобально уникальный механизм, гарантирующий невозможность повторения идентификатора.
Разработка внешнего сервисного уровня
package com.yanggaochao.springboot.learn.springbootjdbclearn.web;
import com.yanggaochao.springboot.learn.springbootjdbclearn.domain.bo.RestCollectionResult;
import com.yanggaochao.springboot.learn.springbootjdbclearn.domain.bo.RestItemResult;
import com.yanggaochao.springboot.learn.springbootjdbclearn.domain.dao.UserDO;
import com.yanggaochao.springboot.learn.springbootjdbclearn.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 用户 Http Rest 接口
*
* @author 杨高超
* @since 2018-03-09
*/
@RestController
@RequestMapping("api/v1/user")
public class UserApi {
@Autowired
private UserService userService;
@RequestMapping(value = "/add", method = RequestMethod.POST)
public RestItemResult<UserDO> add(@RequestBody UserDO user) {
RestItemResult<UserDO> result = new RestItemResult<>();
user = userService.add(user);
if (user != null) {
result.setItem(user);
result.setResult("success");
} else {
result.setMessage("新增用户失败");
result.setResult("failure");
}
return result;
}
@RequestMapping(value = "/update", method = RequestMethod.POST)
public RestItemResult<UserDO> update(@RequestBody UserDO user) {
RestItemResult<UserDO> result = new RestItemResult<>();
user = userService.update(user);
if (user != null) {
result.setItem(user);
result.setResult("success");
} else {
result.setMessage("修改用户失败");
result.setResult("failure");
}
return result;
}
@RequestMapping(value = "/delete/{uuid}", method = RequestMethod.GET)
public RestItemResult<UserDO> delete(@PathVariable Long uuid) {
RestItemResult<UserDO> result = new RestItemResult<>();
if (userService.delete(uuid)) {
result.setResult("success");
} else {
result.setMessage("删除用户失败");
result.setResult("failure");
}
return result;
}
@RequestMapping(value = "/locate/{uuid}", method = RequestMethod.GET)
public RestItemResult<UserDO> locate(@PathVariable Long uuid) {
RestItemResult<UserDO> result = new RestItemResult<>();
UserDO user = userService.locate(uuid);
if (user != null) {
result.setItem(user);
result.setResult("success");
} else {
result.setMessage("查询用户失败");
result.setResult("failure");
}
return result;
}
@RequestMapping(value = "/match/{name}", method = RequestMethod.GET)
public RestCollectionResult<UserDO> match(@PathVariable String name) {
RestCollectionResult<UserDO> result = new RestCollectionResult<>();
List<UserDO> users = userService.matchName(name);
result.setItems(users);
result.setResult("success");
return result;
}
}
Здесь @RestController используется для объявления того, что это класс интерфейса Http Rest. Маршрут вызова для каждого интерфейса формируется путем объединения @RequestMapping в классе и @RequestMapping в методе. Атрибут метода в @RequestMapping метода объявляет метод, вызываемый http. Аннотация @RequestBody автоматически преобразует объект json в данных публикации в объект POJO. @PathVariable автоматически преобразует данные в пути URL-адреса http в параметры метода службы.
Тест интерфейса Http Rest
Тест вызывает службу Http Rest через HttpClient Apache commons.
Вспомогательный класс вызова Http Resst
package com.yanggaochao.springboot.learn.springbootjdbclearn;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Map;
/**
* @author 杨高超
* @since 2018-03-09
*/
public class HttpClientHelper {
/**
* 用 get 方法发起一个http请求
*
* @param url 要访问的 http 的 url
* @return 访问 http 后得到的回应文本
*/
public String httpGetRequest(String url, Map<String, String> headers) {
try {
HttpClient httpclient = new HttpClient();
GetMethod method = new GetMethod(url);
method.setRequestHeader("Content-Type", "application/json; charset=utf-8");
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false));
if (headers != null) {
for (String key : headers.keySet()) {
method.setRequestHeader(key, headers.get(key));
}
}
int statusCode = httpclient.executeMethod(method);
if (statusCode == 200) {
return parseInputStream(method.getResponseBodyAsStream());
} else {
System.out.println(url + " status = " + statusCode);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 用 post 方法发起一个 http 请求
*
* @param url 要访问的 http 的 url
* @param data post 请求中的 data 数据
* @return 访问 http 后得到的回应文本
*/
public String httpPostRequest(String url, String data, Map<String, String> headers) {
try {
HttpClient httpclient = new HttpClient();
PostMethod method = new PostMethod(url);
method.setRequestHeader("Content-Type",
"application/json;charset=UTF-8");
method.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36");
if (headers != null) {
for (String key : headers.keySet()) {
method.setRequestHeader(key, headers.get(key));
}
}
method.setRequestEntity(new StringRequestEntity(data, "json", "utf-8"));
int statusCode = httpclient.executeMethod(method);
if (statusCode == 200) {
return parseInputStream(method.getResponseBodyAsStream());
} else {
System.out.println(url + " status = " + statusCode + parseInputStream(method.getResponseBodyAsStream()));
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 从 java.io.Reader 中解析文本数据
*
* @param rd java.io.Reader 对象
* @throws Exception 发生错误时抛出异常
*/
private String parseReader(Reader rd) throws Exception {
BufferedReader brd = new BufferedReader(rd);
String line;
StringBuilder respongseContext = new StringBuilder();
while ((line = brd.readLine()) != null) {
respongseContext.append(line).append("\n");
}
//rd.close();
if (respongseContext.length() > 0) {
respongseContext.deleteCharAt(respongseContext.length() - 1);
}
return respongseContext.toString();
}
/**
* 从输入流中解析文本数据
*
* @param is 输入流
* @throws Exception 发生错误时抛出异常
*/
private String parseInputStream(InputStream is) throws Exception {
return parseReader(new BufferedReader(new InputStreamReader(is)));
}
}
Здесь нужно добиться вызова метода Http Rest с помощью методов GET и POST.
прецедент
Используйте JUnit для выполнения тестовых случаев. Для реализации теста мы дополнительно добавили следующие maven-зависимости
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jettison</groupId>
<artifactId>jettison</artifactId>
<version>1.3.3</version>
<scope>test</scope>
</dependency>
package com.yanggaochao.springboot.learn.springbootjdbclearn;
import org.codehaus.jettison.json.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
/**
* Description:
*
* @author 杨高超
* @since 2018-03-09
*/
public class UserApiTest {
private String userAddUrl = "http://localhost:3030/security/api/v1/user/add";
private String userLocateUrl = "http://localhost:3030/security/api/v1/user/locate/";
private String userDeleteUrl = "http://localhost:3030/security/api/v1/user/delete/";
private String userUpdateUrl = "http://localhost:3030/security/api/v1/user/update";
private String userMatchUrl = "http://localhost:3030/security/api/v1/user/match/";
JSONObject addUser = new JSONObject();
Long addUserId = null;
List<Long> userIds = new ArrayList<>();
@Before
public void before() throws Exception {
addUser.put("name", "美羊羊");
JSONObject addResultJson = new JSONObject(new HttpClientHelper().httpPostRequest(userAddUrl, addUser.toString(), null));
assert ("success".equals(addResultJson.getString("result")));
addUserId = addResultJson.getJSONObject("item").getLong("id");
JSONObject user = new JSONObject();
user.put("name", "喜羊羊");
addResultJson = new JSONObject(new HttpClientHelper().httpPostRequest(userAddUrl, user.toString(), null));
assert ("success".equals(addResultJson.getString("result")));
userIds.add(addResultJson.getJSONObject("item").getLong("id"));
user.put("name", "灰太狼");
addResultJson = new JSONObject(new HttpClientHelper().httpPostRequest(userAddUrl, user.toString(), null));
assert ("success".equals(addResultJson.getString("result")));
userIds.add(addResultJson.getJSONObject("item").getLong("id"));
}
@Test
public void testUpdateUser() throws Exception {
JSONObject user = new JSONObject();
user.put("name", "霉羊羊");
user.put("id", addUserId);
new HttpClientHelper().httpPostRequest(userUpdateUrl, user.toString(), null);
JSONObject locateResultJson = new JSONObject(new HttpClientHelper().httpGetRequest(userLocateUrl + addUserId, null));
assert (user.getString("name").equals(locateResultJson.getJSONObject("item").getString("name")));
}
@Test
public void testMatchUser() throws Exception {
JSONObject matchResultJson = new JSONObject(new HttpClientHelper().httpGetRequest(userMatchUrl + URLEncoder.encode("羊","UTF-8"), null));
assert (matchResultJson.has("items") && matchResultJson.getJSONArray("items").length() == 2);
matchResultJson = new JSONObject(new HttpClientHelper().httpGetRequest(userMatchUrl + URLEncoder.encode("狼","UTF-8"), null));
assert (matchResultJson.has("items") && matchResultJson.getJSONArray("items").length() == 1);
}
@After
public void after() throws Exception {
if (addUserId != null) {
JSONObject deleteResultJson = new JSONObject(new HttpClientHelper().httpGetRequest(userDeleteUrl + addUserId, null));
assert ("success".equals(deleteResultJson.getString("result")));
}
for (Long userId : userIds) {
JSONObject deleteResultJson = new JSONObject(new HttpClientHelper().httpGetRequest(userDeleteUrl + userId, null));
assert ("success".equals(deleteResultJson.getString("result")));
}
}
}
Здесь в @Test объявлены два тестовых примера, один тестирует функцию модификации пользователя, а другой тестирует функцию пользовательского нечеткого запроса. @Before объявляет подготовительную работу, которую необходимо выполнить перед выполнением каждого тестового примера. Здесь сначала вставьте три фрагмента данных в базу данных, а также протестируйте функцию добавления данных и точного запроса. @After объявляет работу по очистке после выполнения каждого тестового примера. Это в основном для удаления ранее вставленных данных. Здесь синхронно тестируется функция удаления пользователя.
постскриптум
Вот полный пример SpringBoot с использованием JDBC Templet. Если у вас есть опыт использования JDBC Templet под Spring, то большая часть работы по настройке в основном сокращается в Spring.
Код, задействованный в этой статье, был загружен наGitHUBначальство.
Первоначально опубликовано вкороткая книга