Пакетные данные обновления в одной таблице
Обновите несколько фрагментов данных, каждый фрагмент данных отличается
- описание фона
Обычно существует два способа одновременного обновления нескольких фрагментов данных.
- Прокручивайте бизнес-код и обновляйте один за другим
- Обновите все данные за один раз, используя пакетный метод sql, и выполните его за один раз.
更准确的说是一条sql语句来更新所有数据,逐条更新的操作放到数据库端,在业务代码端展现的就是一次性更新所有数据。
Эти два метода имеют свои преимущества и недостатки, я не буду говорить о реализации цикла for в программе, здесь мы в основном представим реализацию второго метода в fluent mybatis и сравнение с реализацией mybatis.
Как реализовать цикл for в java
public class UpdateBatchTest extends BaseTest {
@Autowired
private StudentMapper mapper;
@Test
public void testBatchJavaEach() {
/** 构造多个更新 **/
List<IUpdate> updates = this.newListUpdater();
for (IUpdate update : updates) {
mapper.updateBy(update);
}
}
/**
* 构造多个更新操作
*/
private List<IUpdate> newListUpdater() {
StudentUpdate update1 = new StudentUpdate()
.set.userName().is("user name23").end()
.where.id().eq(23L).end();
StudentUpdate update2 = new StudentUpdate()
.update.userName().is("user name24").end()
.where.id().eq(24L).end();
return Arrays.asList(update1, update2);
}
}
Таким образом, при обновлении большими партиями самой большой проблемой является эффективность, обновление по одному, подключение к базе данных каждый раз, затем обновление, а затем освобождение ресурсов подключения.
SQL, сервер обновляет один за другим
реализация mybatis
С помощью метки цикла, предоставляемой mybatis, одновременно создается несколько SQL-запросов обновления, которые одновременно отправляются на сервер для выполнения.
<update id="updateStudentBatch" parameterType="java.util.List">
<update id="updateStudentBatch" parameterType="java.util.List">
<foreach collection="list" item="item" index="index" open="" close="" separator=";">
update student
<set>
user_name=#{item.userName}
</set>
where id = #{item.id}
</foreach>
</update>
</update>
Определить картограф
public interface StudentBatchMapper {
void updateStudentBatch(List list);
}
Выполнить тестовую проверку
public class UpdateBatchTest extends BaseTest {
@Autowired
private StudentBatchMapper batchMapper;
@Test
public void updateStudentBatch() {
List<StudentEntity> students = Arrays.asList(
new StudentEntity().setId(23L).setUserName("user name23"),
new StudentEntity().setId(24L).setUserName("user name24"));
batchMapper.updateStudentBatch(students);
/** 验证SQL参数 **/
db.table(ATM.table.student).query().eqDataMap(ATM.dataMap.student.table(2)
.id.values(23L, 24L)
.userName.values("user name23", "user name24")
);
}
}
Реализация с использованием FluentMybatis
Пакетное обновление с помощью Fluent Mybatis очень просто, просто передайте массив IUpdate в методе #updateBy.
public class UpdateBatchTest extends BaseTest {
@Autowired
private StudentMapper mapper;
@DisplayName("批量更新同一张表")
@Test
public void testUpdateBatch_same() {
IUpdate[] updates = this.newListUpdater().toArray(new IUpdate[0]);
mapper.updateBy(updates);
/** 验证SQL语句 **/
db.sqlList().wantFirstSql().eq("" +
"UPDATE student SET gmt_modified = now(), user_name = ? WHERE id = ?; " +
"UPDATE student SET gmt_modified = now(), user_name = ? WHERE id = ?"
, StringMode.SameAsSpace);
/** 验证SQL参数 **/
db.table(ATM.table.student).query().eqDataMap(ATM.dataMap.student.table(2)
.id.values(23L, 24L)
.userName.values("user name23", "user name24")
);
}
}
要实现批量更新,首先得设置mysql支持批量操作,在jdbc url链接中附加&allowMultiQueries=true属性
例如:
jdbc:mysql://localhost:3306/testdb?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
Обновление с использованием метода mysql Case When then
UPDATE student
SET gmt_modified = now(),
address = case id when 1 then 'address 1' when 2 then 'address 2' when 3 then 'address 3' end
WHERE id in (1, 2, 3)
Вышеприведенный оператор sql использует случай mysql, когда затем синтаксис для обновления 3 записей в пакетах и установки разных значений адреса в соответствии со значением id.
нативная реализация mybatis
Если вы используете синтаксис xml mybatis для реализации, файл xml должен быть выражен следующим образом:
- XML-файл
<update id="updateBatchByIds" parameterType="list">
update student
<trim prefix="set" suffixOverrides=",">
<trim prefix="address =case id" suffix="end,">
<foreach collection="list" item="item" index="index">
<if test="item.id!=null">
when #{item.id} then #{item.address}
</if>
</foreach>
</trim>
</trim>
<trim prefix="age =case id" suffix="end,">
<foreach collection="list" item="item" index="index">
<if test="item.id!=null">
when #{item.id} then #{item.age}
</if>
</foreach>
</trim>
where id in
<foreach collection="list" item="item" index="index" separator="," open="(" close=")">
#{item.id}
</foreach>
</update>
- Определить картограф
public interface StudentBatchMapper {
int updateBatchByIds(List<StudentEntity> list);
}
- проверять
public class CaseFuncTest extends BaseTest {
@Autowired
private StudentBatchMapper batchMapper;
@Test
public void test_mybatis_batch() {
batchMapper.updateBatchByIds(Arrays.asList(
new StudentEntity().setId(1L).setAddress("address 1").setAge(23),
new StudentEntity().setId(2L).setAddress("address 2").setAge(24),
new StudentEntity().setId(3L).setAddress("address 3").setAge(25)
));
/** 验证执行的SQL语句 **/
db.sqlList().wantFirstSql().eq("" +
"update student " +
"set address =case id when ? then ? when ? then ? when ? then ? end, " +
"age =case id when ? then ? when ? then ? when ? then ? end " +
"where id in ( ? , ? , ? )"
, StringMode.SameAsSpace);
}
}
Реализация с использованием Fluent Mybatis
public class CaseFuncTest extends BaseTest {
@Autowired
private StudentMapper mapper;
@Test
public void test_fluentMybatisBatch() throws Exception {
final String CaseWhen = "case id " +
"when 1 then ? " +
"when 2 then ? " +
"else ? end";
StudentUpdate update = new StudentUpdate()
.set.address().applyFunc(CaseWhen, "address 1", "address 2", "address 3")
.set.age().applyFunc(CaseWhen, 23, 24, 25)
.end()
.where.id().in(new int[]{1, 2, 3}).end();
mapper.updateBy(update);
/** 验证执行的SQL语句 **/
db.sqlList().wantFirstSql()
.eq("UPDATE student " +
"SET gmt_modified = now(), " +
"address = case id when 1 then ? when 2 then ? else ? end, " +
"age = case id when 1 then ? when 2 then ? else ? end " +
"WHERE id IN (?, ?, ?)",
StringMode.SameAsSpace);
}
}
Просто передайте оператор case when в applyFunc и соответствующие параметры (соответствующие предварительно скомпилированному заполнителю '?' в операторе case when)
Если бизнес-запись представляет собой список сущностей или список карт, ее можно преобразовать в массив с помощью потоковой функции java8. Пример выглядит следующим образом:
public class CaseFuncTest extends BaseTest {
@Autowired
private StudentMapper mapper;
@Test
public void test_fluentMybatisBatch2() throws Exception {
List<StudentEntity> students = Arrays.asList(
new StudentEntity().setId(1L).setAddress("address 1").setAge(23),
new StudentEntity().setId(2L).setAddress("address 2").setAge(24),
new StudentEntity().setId(3L).setAddress("address 3").setAge(25));
final String CaseWhen = "case id " +
"when 1 then ? " +
"when 2 then ? " +
"else ? end";
StudentUpdate update = new StudentUpdate()
.set.address().applyFunc(CaseWhen, getFields(students, StudentEntity::getAddress))
.set.age().applyFunc(CaseWhen, getFields(students, StudentEntity::getAge))
.end()
.where.id().in(getFields(students, StudentEntity::getId)).end();
mapper.updateBy(update);
// 验证SQL语句
db.sqlList().wantFirstSql()
.eq("UPDATE student " +
"SET gmt_modified = now(), " +
"address = case id when 1 then ? when 2 then ? else ? end, " +
"age = case id when 1 then ? when 2 then ? else ? end " +
"WHERE id IN (?, ?, ?)",
StringMode.SameAsSpace);
// 验证参数
db.sqlList().wantFirstPara()
.eqReflect(new Object[]{"address 1", "address 2", "address 3", 23, 24, 25, 1L, 2L, 3L});
}
private Object[] getFields(List<StudentEntity> students, Function<StudentEntity, Object> getField) {
return students.stream().map(getField).toArray(Object[]::new);
}
}
При использовании Fluent Mybatis не нужно писать дополнительные xml-файлы и маппер (достаточно использовать файл маппера, сгенерированный фреймворком). С точки зрения бизнес-логики нет смысла разделения из-за дополнительных xml-файлов.
Пакетное обновление различных данных таблицы
В приведенном выше примере используются mybatis и fluent mybatis, чтобы продемонстрировать, что если данные одной и той же таблицы обновляются пакетами с помощью разных методов, обновление в fluent mybatis не ограничивается одной и той же таблицей. Произвольные обновления таблицы могут быть переданы в функцию #updateBy(IUpdate... update).
public class UpdateBatchTest extends BaseTest {
@Autowired
private StudentMapper mapper;
@DisplayName("批量更新不同表")
@Test
public void testUpdateBatch_different() {
StudentUpdate update1 = new StudentUpdate()
.set.userName().is("user name23").end()
.where.id().eq(23L).end();
HomeAddressUpdate update2 = new HomeAddressUpdate()
.set.address().is("address 24").end()
.where.id().eq(24L).end();
/** 执行不同表的批量更新 **/
mapper.updateBy(update1, update2);
/** 验证实际执行的预编译SQL语句**/
db.sqlList().wantFirstSql().eq("" +
"UPDATE student SET gmt_modified = now(), user_name = ? WHERE id = ?; " +
"UPDATE home_address SET gmt_modified = now(), address = ? WHERE id = ?", StringMode.SameAsSpace);
db.table(ATM.table.student).query().eqDataMap(ATM.dataMap.student.table(2)
.id.values(23L, 24L)
.userName.values("user name23", "user")
);
/** 验证实际执行预编译SQL入参值 **/
db.table(ATM.table.homeAddress).query().eqDataMap(ATM.dataMap.homeAddress.table(2)
.id.values(23, 24)
.address.values("address", "address 24")
);
}
}
В примере обновляются 2 таблицы: student и home_address.
Связь
Пакетная функция, описанная в статье, доступна в версии 1.4.11 fluent mybatis.
Свободная документация и примеры Mybatis
*** Спасибо пользователю сети Lunzi Lunzi за предложение ***