Начало работы с Spring (15): Использование Spring JDBC для работы с базами данных

Spring

В предыдущих блогах этой серии мы никогда не объясняли метод работы с базой данных, но в реальной работе почти все системы неотделимы от сохраняемости данных, поэтому очень важно освоить метод работы с базой данных.

В Spring существует много способов работы с базой данных.Мы можем использовать JDBC, Hibernate, MyBatis или другие среды хранения данных.Целью этого блога является объяснение того, как работать с базой данных через JDBC в Spring.

1. Решение проблемы при сборке проекта

Прежде чем объяснять JDBC, давайте сначала решим проблему, потому что следующая ошибка была зарегистрирована, когда программа, которая изначально была собрана нормально, была перестроена и упакована:

После поиска информации в Интернете было сказано, что версия зависимости конфликтует, поэтому я проверил зависимость Spring, добавленную ранее в pom.xml:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>
<!--spring aop支持-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.1.8.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>4.3.18.RELEASE</version>
    <scope>test</scope>
</dependency>

Версия spring-aop — 5.1.8.RELEASE, а версия остальных трех пакетов — 4.3.18.RELEASE, а версия spring-aop также изменена на 4.3.18.RELEASE:

<!--spring aop支持-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>

На этом этапе пересоберите пакет, ошибок больше не будет, и пакет выполнен успешно:

Однако приведенные выше зависимости также можно упростить следующим образом:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>4.3.18.RELEASE</version>
    <scope>test</scope>
</dependency>

Поскольку пакет spring-webmvc уже включает в себя spring-context и spring-aop, нет необходимости повторно добавлять эти две зависимости:

2. Настройте источник данных

Сначала выполните следующую инструкцию, чтобы создать базу данных MySql spring_action_db:

CREATE DATABASE spring_action_db DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

Затем выполните следующую инструкцию, чтобы создать книгу таблиц:

use spring_action_db;

create table Book
(
  book_id     bigint auto_increment comment '书籍id',
  book_name   varchar(50) not null comment '书名',
  author      varchar(50) not null comment '作者',
  create_by   varchar(20) not null comment '创建人',
  create_time datetime    not null comment '创建时间',
  modify_by   varchar(20) not null comment '修改人',
  modify_time datetime    not null comment '修改时间',
  constraint Book_pk
    primary key (book_id)
)
  comment '书籍';

Когда будете готовы, создайте новый класс конфигурации для настройки источника данных:

package chapter10.config;

import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("chapter10")
public class DataSourceConfig {
    @Bean
    public BasicDataSource dataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring_action_db");
        dataSource.setUsername("root");
        dataSource.setPassword("root");

        return dataSource;
    }
}

Поскольку мы используем базу данных MySql, для имени драйвера установлено значение: com.mysql.jdbc.Driver.

Если вы используете другой тип базы данных, вам необходимо изменить его на соответствующее имя.

Так как используется драйвер MySql, нам необходимо добавить в pom.xml следующие зависимости, иначе соединение не будет получено при обращении к базе данных:

<!-- MySql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.46</version>
</dependency>

3. Используйте необработанный код JDBC

Сначала создайте новый класс объектов базы данных Book:

package chapter10.domain;

import java.util.Date;

public class Book {
    private Long bookId;

    private String bookName;

    private String author;

    private String createBy;

    private Date createTime;

    private String modifyBy;

    private Date modifyTime;

    public Book(String bookName, String author, String createBy) {
        this.bookName = bookName;
        this.author = author;
        this.createBy = createBy;
        this.createTime = new Date();
        this.modifyBy=createBy;
        this.modifyTime=new Date();
    }
    
    public Book(Long bookId, String bookName, String author, String modifyBy) {
        this.bookId = bookId;
        this.bookName = bookName;
        this.author = author;
        this.modifyBy = modifyBy;
    }
    
    public Book() {
        
    }

    // 省略get和set方法
}

Затем определите интерфейс доступа к данным BookRepository и пока добавьте только метод addBook:

package chapter10.db;

import chapter10.domain.Book;

public interface BookRepository {
    void addBook(Book book);
}

3.1 Новые данные

Новый класс реализации доступа к данным JdbcBookRepository выглядит следующим образом:

package chapter10.db.jdbc;

import chapter10.db.BookRepository;
import chapter10.domain.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;

@Repository
public class JdbcBookRepository implements BookRepository {
    private static final String SQL_INSERT_BOOK =
            "INSERT INTO book(book_name, author, create_by, create_time, modify_by, modify_time) VALUES (?,?,?,?,?,?);";

    @Autowired
    private DataSource dataSource;

    @Override
    public void addBook(Book book) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(new Date());

            connection = dataSource.getConnection();
            preparedStatement = connection.prepareStatement(SQL_INSERT_BOOK);
            preparedStatement.setString(1, book.getBookName());
            preparedStatement.setString(2, book.getAuthor());
            preparedStatement.setString(3, book.getCreateBy());
            preparedStatement.setTimestamp(4, new Timestamp(calendar.getTimeInMillis()));
            preparedStatement.setString(5, book.getModifyBy());
            preparedStatement.setTimestamp(6, new Timestamp(calendar.getTimeInMillis()));

            preparedStatement.execute();
        } catch (SQLException e) {
            // 异常处理相关代码
        } finally {
            try {
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                // 异常处理相关代码
            }
        }
    }
}

Примечание. Класс снабжен аннотацией @Repository, чтобы Spring мог сканировать его и зарегистрировать как bean-компонент.

Стоит отметить, что в этом коде мы дважды поймали SQLException, потому чтоconnection = dataSource.getConnection();,preparedStatement.execute();,preparedStatement.close();,connection.close();Оба будут генерировать проверенное исключение SQLException, поэтому метод должен быть перехвачен, иначе компиляция завершится ошибкой:

Connection getConnection() throws SQLException;

boolean execute() throws SQLException;

void close() throws SQLException;

void close() throws SQLException;

Наконец, новый класс модульного теста BookRepositoryTest выглядит следующим образом:

package chapter10;

import chapter10.config.DataSourceConfig;
import chapter10.db.BookRepository;
import chapter10.domain.Book;
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(classes = DataSourceConfig.class)
public class BookRepositoryTest {
    @Autowired
    private BookRepository bookRepository;

    @Test
    public void testAddBook() {
        Book book = new Book("Spring实战(第4版)", "Craig Walls", "申城异乡人");

        bookRepository.addBook(book);

        book = new Book("Java EE开发的颠覆者:Spring Boot实战", "汪云飞", "申城异乡人");

        bookRepository.addBook(book);

        book = new Book("RabbitMQ实战指南", "朱忠华", "申城异乡人");

        bookRepository.addBook(book);
    }
}

Запускаем тестовый метод testAddBook(), данные успешно добавлены в базу:

3.2 Обновление данных

Сначала добавьте метод обновления в поставщик данных BookRepository:

void updateBook(Book book);

Затем реализуйте метод в классе реализации доступа к данным JdbcBookRepository:

private static final String SQL_UPDATE_BOOK =
            "UPDATE Book SET book_name = ?,author = ?,modify_by = ?,modify_time=? WHERE book_id = ?;";

@Override
public void updateBook(Book book) {
    Connection connection = null;
    PreparedStatement preparedStatement = null;

    try {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());

        connection = dataSource.getConnection();
        preparedStatement = connection.prepareStatement(SQL_UPDATE_BOOK);
        preparedStatement.setString(1, book.getBookName());
        preparedStatement.setString(2, book.getAuthor());
        preparedStatement.setString(3, book.getModifyBy());
        preparedStatement.setTimestamp(4, new Timestamp(calendar.getTimeInMillis()));
        preparedStatement.setLong(5, book.getBookId());

        preparedStatement.execute();
    } catch (SQLException e) {
        // 异常处理相关代码
    } finally {
        try {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            // 异常处理相关代码
        }
    }
}

Вы обнаружили, что его код почти такой же, как и предыдущий только что добавленный код, и он должен дважды перехватывать проверенное исключение SQLException?Люди с чистотой кода не могут не хотеть рефакторинга, ха-ха.

Наконец, добавьте тестовый метод testUpdateBook в тестовый класс BookRepositoryTest следующим образом:

@Test
public void testUpdateBook() {
    Book book = new Book(1L, "Spring实战(第4版)", "Craig Walls", "zwwhnly");

    bookRepository.updateBook(book);

    book = new Book(2L, "Java EE开发的颠覆者:Spring Boot实战", "汪云飞", "zwwhnly");

    bookRepository.updateBook(book);

    book = new Book(3L, "RabbitMQ实战指南", "朱忠华", "zwwhnly");

    bookRepository.updateBook(book);
}

Выполните тестовый метод, и обновление данных пройдет успешно:

3.3 Поиск данных

Сначала добавьте метод обновления в поставщик данных BookRepository:

Book findBook(long bookId);

Затем реализуйте метод в классе реализации доступа к данным JdbcBookRepository:

private static final String SQL_SELECT_BOOK =
            "SELECT book_id,book_name,author,create_by,create_time,modify_by,modify_time FROM book WHERE book_id = ?;";

@Override
public Book findBook(long bookId) {
    Connection connection = null;
    PreparedStatement preparedStatement = null;

    ResultSet resultSet = null;
    Book book = null;
    try {
        connection = dataSource.getConnection();
        preparedStatement = connection.prepareStatement(SQL_SELECT_BOOK);
        preparedStatement.setLong(1, bookId);

        resultSet = preparedStatement.executeQuery();

        if (resultSet.next()) {
            book = new Book();
            book.setBookId(resultSet.getLong("book_id"));
            book.setBookName(resultSet.getString("book_name"));
            book.setAuthor(resultSet.getString("author"));
            book.setCreateBy(resultSet.getString("create_by"));
            book.setCreateTime(resultSet.getTimestamp("create_time"));
            book.setModifyBy(resultSet.getString("modify_by"));
            book.setModifyTime(resultSet.getTimestamp("modify_time"));
        }
    } catch (SQLException e) {
        // 异常处理相关代码
    } finally {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (preparedStatement != null) {
                preparedStatement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            // 异常处理相关代码
        }
    }

    return book;
}

Вы обнаружили, что его код в основном такой же, как и предыдущий новый и обновленный код, и он должен дважды перехватывать проверенное исключение SQLException, кто-нибудь с чистым кодом начал рефакторинг, ха-ха.

Наконец, добавьте тестовый метод testFindBook в тестовый класс BookRepositoryTest следующим образом:

@Test
public void testFindBook() {
    Book book = bookRepository.findBook(1L);
    Assert.assertNotNull(book);
    Assert.assertEquals(book.getBookName(), "Spring实战(第4版)");
}

Выполните тестовый метод, и запрос данных будет успешным:

4. Использование шаблонов JDBC

После использования оригинального JDBC для работы с базой данных многие студенты с чистым кодом не могут не начать рефакторинг, потому что большая часть кода является шаблонным кодом, и только небольшая часть связана с бизнес-логикой.Хорошая новость заключается в том, что Spring помог нам провести рефакторинг.После построения Spring абстрагирует шаблонный код доступа к данным в класс шаблона, и мы можем использовать класс шаблона напрямую, тем самым упрощая код JDBC.

4.1 Новые данные

Сначала добавьте следующую конфигурацию в класс конфигурации DataSourceConfig:

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}

Затем удалите аннотацию @Repository во вновь созданном классе JdbcBookRepository.

Затем создайте новый класс реализации доступа к данным JdbcTemplateBookRepository следующим образом:

package chapter10.db.jdbc;

import chapter10.db.BookRepository;
import chapter10.domain.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.stereotype.Repository;

import java.sql.Date;

@Repository
public class JdbcTemplateBookRepository implements BookRepository {
    private static final String SQL_INSERT_BOOK =
            "INSERT INTO book(book_name, author, create_by, create_time, modify_by, modify_time) VALUES (?,?,?,?,?,?);";

    @Autowired
    private JdbcOperations jdbcOperations;

    @Override
    public void addBook(Book book) {
        jdbcOperations.update(SQL_INSERT_BOOK, book.getBookName(),
                book.getAuthor(),
                book.getCreateBy(),
                new Date(System.currentTimeMillis()),
                book.getModifyBy(),
                new Date(System.currentTimeMillis()));
    }
}

Примечание. Класс снабжен аннотацией @Repository, чтобы Spring мог сканировать его и зарегистрировать как bean-компонент.

Это очень лаконично?От предыдущей оптимизации кода до текущего кода студенты с чистотой кода, вероятно, счастливы умереть.

Поскольку в предыдущем тестовом классе BookRepositoryTest мы внедрили интерфейс, поэтому мы можем напрямую обращаться к методу реализации только что созданного класса JdbcTemplateBookRepository без изменения кода тестового класса:

@Autowired
private BookRepository bookRepository;

Запускаем предыдущий тестовый метод testAddBook(), данные успешно добавляются в базу:

4.2 Обновление данных

Добавьте следующий код в класс реализации доступа к данным JdbcTemplateBookRepository:

private static final String SQL_UPDATE_BOOK =
            "UPDATE Book SET book_name = ?,author = ?,modify_by = ?,modify_time=? WHERE book_id = ?;";

@Override
public void updateBook(Book book) {
    jdbcOperations.update(SQL_UPDATE_BOOK, book.getBookName(),
            book.getAuthor(),
            book.getModifyBy(),
            new Timestamp(System.currentTimeMillis()),
            book.getBookId());
}

Затем просто измените предыдущий тестовый метод testUpdateBook():

@Test
public void testUpdateBook() {
    Book book = new Book(4L, "Spring实战(第4版)", "Craig Walls", "zwwhnly");

    bookRepository.updateBook(book);

    book = new Book(5L, "Java EE开发的颠覆者:Spring Boot实战", "汪云飞", "zwwhnly");

    bookRepository.updateBook(book);

    book = new Book(6L, "RabbitMQ实战指南", "朱忠华", "zwwhnly");

    bookRepository.updateBook(book);
}

Запустите предыдущий тестовый метод testUpdateBook(), и обновление данных пройдет успешно:

4.3 Поиск данных

Добавьте следующий код в класс реализации доступа к данным JdbcTemplateBookRepository:

private static final String SQL_SELECT_BOOK =
            "SELECT book_id,book_name,author,create_by,create_time,modify_by,modify_time FROM book WHERE book_id = ?;";

@Override
public Book findBook(long bookId) {
    return jdbcOperations.queryForObject(SQL_SELECT_BOOK, new BookRowMapper(), bookId);
}

private static final class BookRowMapper implements RowMapper<Book> {

    @Override
    public Book mapRow(ResultSet resultSet, int i) throws SQLException {
        Book book = new Book();
        book.setBookId(resultSet.getLong("book_id"));
        book.setBookName(resultSet.getString("book_name"));
        book.setAuthor(resultSet.getString("author"));
        book.setCreateBy(resultSet.getString("create_by"));
        book.setCreateTime(resultSet.getTimestamp("create_time"));
        book.setModifyBy(resultSet.getString("modify_by"));
        book.setModifyTime(resultSet.getTimestamp("modify_time"));


        return book;
    }
}

Запустите предыдущий тестовый метод testFindBook(), и запрос данных будет выполнен успешно:

5. Исходный код и ссылка

Адрес источника:GitHub.com/Где находится Ухань/SPR…, добро пожаловать на скачивание.

Весна в действии (4-е издание) Крейга Уоллса