Подробное объяснение позиции удаления JPA в серии руководств по SpringBoot.

Spring Boot

оригинал:190702-Подробное объяснение положения удаления JPA в серии руководств по SpringBoot.

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

Вообще говоря, не рекомендуется физически удалять данные (удалять записи непосредственно из таблицы).В нынешнюю эпоху данных — это деньги, более распространено добавить поле в таблицу для представления статуса, а затем изменить это поле на представляют собой запись.Вполне ли возможно реализовать логическое удаление, причины этого следующие

  • Физическое удаление, если есть проблема, это более неприятно восстановить
  • Нет никакой гарантии, что код должен быть точным. Когда есть проблема, удалите неправильные данные, затем GG
  • Удаление данных приведет к перестроению индекса.
  • База данных InnoDB только помечает удаленные данные как удаленные и фактически не освобождает занимаемое дисковое пространство, что приводит к постоянному росту файлов базы данных InnoDB и фрагментации таблиц.
  • Логическое удаление для сохранения данных для последующего извлечения или анализа данных.

I. Подготовка окружающей среды

Прежде чем начать, конечно, вы должны подготовить базовую среду, такую ​​как использование mysql для установки и тестирования, создание проектов проектов SpringBoot, настройка информации о конфигурации и т. д. Подробную информацию о создании проектов см. в предыдущей статье.

Давайте кратко рассмотрим конфигурацию, необходимую в процессе добавления записей.

1. Подготовка стола

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

CREATE TABLE `money` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名',
  `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱',
  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
  `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

2. Конфигурация проекта

Информация о конфигурации немного отличается от предыдущей. Мы добавили более подробную печать журнала; основная цель этой статьи — сосредоточиться на использовании состояния добавления записей. Инструкции по настройке мы объясним отдельно позже.

## DataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=
## jpa相关配置
spring.jpa.database=MYSQL
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
spring.jackson.serialization.indent_output=true
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

3. Подготовка данных

Модификация данных, поэтому мы сначала вставляем две части данных в таблицу для последующих операций.

INSERT INTO `money` (`id`, `name`, `money`, `is_deleted`, `create_at`, `update_at`)
VALUES
	(20, 'jpa 一灰灰5', 2323, 0, '2019-07-02 08:42:41', '2019-07-02 08:42:41'),
	(21, 'jpa 一灰灰6', 2333, 0, '2019-07-02 08:42:41', '2019-07-02 08:42:41'),
	(22, 'jpa 一灰灰7', 6666, 0, '2019-07-02 08:42:41', '2019-07-02 08:42:41'),
	(23, 'jpa 一灰灰8', 2666, 0, '2019-07-02 08:42:41', '2019-07-02 08:42:41');

II. Удалить учебное пособие

Упомянутые ниже удаления — это все физические удаления, под которыми можно понимать прямое стирание некоторых записей из таблицы (не говоря уже о том, что после удаления нет возможности восстановиться).Для четырех операций CURD, кроме чтения, остальные три вставка, обновление и удаление добавят блокировки записи (как правило, будут задействованы блокировки строк и блокировки промежутков, как будет видно позже, эти три операции требуют отображения объявлений)

1. Ассоциация таблиц POJO

Предыдущая вставка представила пошаговый процесс создания POJO, и были определены соответствующие аннотации.Результаты размещены непосредственно ниже.

@Data
@DynamicUpdate
@DynamicInsert
@Entity
@Table(name = "money")
public class MoneyPO {
    @Id
    // 如果是auto,则会报异常 Table 'mysql.hibernate_sequence' doesn't exist
    // @GeneratedValue(strategy = GenerationType.AUTO)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @Column(name = "name")
    private String name;

    @Column(name = "money")
    private Long money;

    @Column(name = "is_deleted")
    private Byte isDeleted;

    @Column(name = "create_at")
    @CreatedDate
    private Timestamp createAt;

    @Column(name = "update_at")
    @CreatedDate
    private Timestamp updateAt;

}

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

  • @DataОн относится к аннотации ломбока, не имеет ничего общего с jpa и генерируется автоматически.getter/setter/equals/hashcode/tostringи т.д. метод
  • @Entity, @TableАннотация jpa указывает, что этот класс связан с таблицей базы данных, а конкретное совпадение - это таблицаmoney
  • @Id @GeneratedValueФункция и автоинкрементный первичный ключ
  • @ColumnУказывает, что этот атрибут соответствует столбцу в таблице
  • @CreateDateСоздание метки времени по умолчанию на основе текущего времени

2. Объявление API репозитория

Далее мы создаем новый API, который наследуется отCurdRepository, Затем передайте этот API для работы с базой данных

public interface MoneyDeleteRepository extends CrudRepository<MoneyPO, Integer> {
    /**
     * 查询测试
     * @param id
     * @return
     */
    List<MoneyPO> queryByIdGreaterThanEqual(int id);
}

3. Используйте осанку

Сначала напишите метод для запроса данных, чтобы проверить, были ли они удалены после того, как мы их удалили.

private void showLeft() {
    List<MoneyPO> records = moneyDeleteRepository.queryByIdGreaterThanEqual(20);
    System.out.println(records);
}

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

[MoneyPO(id=20, name=jpa 一灰灰5, money=2323, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0), MoneyPO(id=21, name=jpa 一灰灰6, money=2333, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0), MoneyPO(id=22, name=jpa 一灰灰7, money=6666, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0), MoneyPO(id=23, name=jpa 一灰灰8, money=2666, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0)]

А. Удалить в соответствии с идентификатором первичного ключа

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

private void deleteById() {
    // 直接根据id进行删除
    moneyDeleteRepository.deleteById(21);
    showLeft();
}

После завершения выполнения результаты вывода следующие, вы можете узнать, сравнив предыдущий выводid=21запись пользователя была удалена

[MoneyPO(id=20, name=jpa 一灰灰5, money=2323, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0), MoneyPO(id=22, name=jpa 一灰灰7, money=6666, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0), MoneyPO(id=23, name=jpa 一灰灰8, money=2666, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0)]

Тогда естественно возникает вопрос, а что если записи, соответствующей этому id, не существует?

Выполните приведенный выше код еще раз и обнаружите, что возникает исключение.

Почему это так? Отлаживаемся, реализация вызова по умолчаниюSimpleJpaRepository, его исходный код такой

// 类为: org.springframework.data.jpa.repository.support.SimpleJpaRepository
@Transactional
public void deleteById(ID id) {

	Assert.notNull(id, ID_MUST_NOT_BE_NULL);

	delete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
			String.format("No %s entity with id %s exists!", entityInformation.getJavaType(), id), 1)));
}

@Transactional
public void delete(T entity) {

	Assert.notNull(entity, "The entity must not be null!");
	em.remove(em.contains(entity) ? entity : em.merge(entity));
}

Как видно из исходного кода, это сначала опрашивается по id.Если соответствующая запись не существует, сразу выбрасывается исключение, когда оно существует, используется логика удаления;

Если мы хотим удалить несуществующие данные, не сообщайте об ошибке, что я могу сделать?

  • Пользовательская реализация наследованияSimpleJpaRepositoryкласс, который переопределяет метод удаления
@Repository
@Transactional(readOnly = true)
public class MoneyDeleteRepositoryV2 extends SimpleJpaRepository<MoneyPO, Integer> {

    @Autowired
    public MoneyDeleteRepositoryV2(EntityManager em) {
        this(JpaEntityInformationSupport.getEntityInformation(MoneyPO.class, em), em);
    }

    public MoneyDeleteRepositoryV2(JpaEntityInformation<MoneyPO, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
    }

    public MoneyDeleteRepositoryV2(Class<MoneyPO> domainClass, EntityManager em) {
        super(domainClass, em);
    }

    @Override
    public void deleteById(Integer id) {
        Optional<MoneyPO> rec = findById(id);
        rec.ifPresent(super::delete);
    }
}

Затем вы можете снова вызвать вышеуказанный метод, не демонстрируя конкретный тестовый пример, исходный код можно посмотреть в проекте проекта 👉исходный код

б. Удаление условного приговора

Хотя идентификатор удалять более осмотрительно, но его нельзя избежать, в некоторых случаях необходимо удалить на основе других полей, например, мы хотим удалить имяjpa 一灰灰7данные, то нам нужноMoneyDeleteRepositoryдобавить метод

/**
 * 根据name进行删除
 *
 * @param name
 */
void deleteByName(String name);

Вот краткое упоминание правил именования этого метода, которые будут объяснены более подробно позже в запросе;

  • deleteУказывает, что выполняется операция удаления
  • ByУказывает, что условия основаны на поле
  • NameЭто имеет соответствие свойств в POJO

Приведенный выше способ, если переведен на SQL, эквивалентноdelete from money where name=xx

Метод вызова такой же, как и раньше, а именно:

private void deleteByName() {
    moneyDeleteRepository.deleteByName("jpa 一灰灰7");
    showLeft();
}

Затем мы выполнили вышеуказанный тест и обнаружили, что он не был успешным, и было сообщено об ошибке.

Изучив предыдущее обновление, я знаю, что необходимо отображать аннотацию добавления вещи, мы ее прямо здесь добавляем.Repositoryсередина

/**
 * 根据name进行删除
 *
 * @param name
 */
@Transactional
void deleteByName(String name);

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

Hibernate: select moneypo0_.id as id1_0_, moneypo0_.create_at as create_a2_0_, moneypo0_.is_deleted as is_delet3_0_, moneypo0_.money as money4_0_, moneypo0_.name as name5_0_, moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.name=?
Hibernate: delete from money where id=?
Hibernate: select moneypo0_.id as id1_0_, moneypo0_.create_at as create_a2_0_, moneypo0_.is_deleted as is_delet3_0_, moneypo0_.money as money4_0_, moneypo0_.name as name5_0_, moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.id>=?
[MoneyPO(id=20, name=jpa 一灰灰5, money=2323, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0), MoneyPO(id=23, name=jpa 一灰灰8, money=2666, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0)]

Из последних оставшихся записей имяjpa 一灰灰7был удалён, а потом посмотрите на ранее удалённый sql, найдёте интересное место,deleteByNameЭтот метод, переведенный в sql, становится двумя

  • select * from money where name=xxxПервые записи запроса по имени
  • delete from money where id = xxxПо идентификатору предыдущей записи запроса удалить запись

в. Сравнить удалить

Затем продемонстрируйте удаление денег в[2000,3000]Запись интервала, то наш новый вход может быть

/**
 * 根据数字比较进行删除
 *
 * @param low
 * @param big
 */
@Transactional
void deleteByMoneyBetween(Long low, Long big);

Благодаря именованию методов вы также можете просто узнать, что приведенное выше эквивалентно sqldelete from money where money between xxx and xxx

Код теста

private void deleteByCompare() {
    moneyDeleteRepository.deleteByMoneyBetween(2000L, 3000L);
    showLeft();
}

выходной журнал

Hibernate: select moneypo0_.id as id1_0_, moneypo0_.create_at as create_a2_0_, moneypo0_.is_deleted as is_delet3_0_, moneypo0_.money as money4_0_, moneypo0_.name as name5_0_, moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.money between ? and ?
Hibernate: delete from money where id=?
Hibernate: delete from money where id=?
Hibernate: select moneypo0_.id as id1_0_, moneypo0_.create_at as create_a2_0_, moneypo0_.is_deleted as is_delet3_0_, moneypo0_.money as money4_0_, moneypo0_.name as name5_0_, moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.id>=?
[]

Как видно из склеенного sql, приведенная выше логика эквивалентна выполнению сначала запроса, а потом удаления по одному по id....

4. Резюме

Мы удалили условия, объявив методы;

  • Для удаления требуются явные операторные транзакции@Transactional
  • Удаление несуществующей записи вызовет исключение
  • При объявлении метода удаления это фактически эквивалентно сначала запросу записи, а затем ее точному удалению в соответствии с идентификатором записи.

II. Другое

исходный код

Связанные сообщения в блоге

1. Серый блог

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

Ниже представлен серый личный блог, в котором записываются все посты в блоге по учебе и работе, приглашаю всех посетить

  • One Grey BlogПерсональный блогblog.hhui.top
  • Серый блог - специальный весенний блогspring.hhui.top

一灰灰blog