mybatis-mapper / mapper

MyBatis Mapper
https://mapper.mybatis.io
Apache License 2.0
325 stars 47 forks source link

如何实现根据Example更新单个字段的值? #18

Closed li362692680 closed 1 year ago

li362692680 commented 2 years ago

如何实现根据Example更新单个字段的值?目前的更新需要传入实体对象

abel533 commented 2 years ago

可以自己实现,虽然实现比较容易,但是暂时不考虑添加到mapper中,你可以使用下面的实现。

实现代码如下,定义通用接口:

@Lang(Caching.class)
@UpdateProvider(type = MyExampleProvider.class, method = "updateFieldByExample")
<T, R> int updateFieldByExample(@Param("column") Fn<T, R> column, @Param("value") R value, @Param("example") Example<T> example);

MyExampleProvider 中实现 updateFieldByExample 方法:

public static String updateFieldByExample(ProviderContext providerContext) {
  return SqlScript.caching(providerContext, new SqlScript() {
    @Override
    public String getSql(EntityTable entity) {
      return ifTest("example.startSql != null and example.startSql != ''", () -> "${example.startSql}")
          + "UPDATE " + entity.table()
          + set(() -> "${column.toColumn()} = #{value}")
          + variableNotNull("example", "Example cannot be null")
          //是否允许空条件,默认允许,允许时不检查查询条件
          + (entity.getProp("updateByExample.allowEmpty", true) ?
          "" : variableIsFalse("example.isEmpty()", "Example Criteria cannot be empty"))
          + ExampleProvider.UPDATE_BY_EXAMPLE_WHERE_CLAUSE
          + ifTest("example.endSql != null and example.endSql != ''", () -> "${example.endSql}");
    }
  });
}

你可以对比上面的方法和 在 ExampleProvider 中的 updateByExample 方法,就是简单改了改 set() 中的内容,参考现有功能扩展很容易,理解里面写法后可以更灵活。

用法:

@Test
public void testUpdateByExample() {
  SqlSession sqlSession = getSqlSession();
  try {
    ExampleMapper<User, Example<User>> exampleMapper = sqlSession.getMapper(UserMapper.class);
    Example<User> example = new Example();
    example.createCriteria().andEqualTo(User::getId, 1L);
    //userName字段对应的列为name
    Assert.assertEquals(1, exampleMapper.updateFieldByExample(User::getUserName, "测试", example));
    sqlSession.rollback();
  } finally {
    //不要忘记关闭sqlSession
    sqlSession.close();
  }
}

执行的SQL:

DEBUG [main] - ==>  Preparing: UPDATE user SET name = ? WHERE ( id = ? )
DEBUG [main] - ==> Parameters: 测试(String), 1(Long)
DEBUG [main] - <==    Updates: 1
li362692680 commented 2 years ago

可以自己实现,虽然实现比较容易,但是暂时不考虑添加到mapper中,你可以使用下面的实现。

实现代码如下,定义通用接口:

@Lang(Caching.class)
@UpdateProvider(type = MyExampleProvider.class, method = "updateFieldByExample")
<T, R> int updateFieldByExample(@Param("column") Fn<T, R> column, @Param("value") R value, @Param("example") Example<T> example);

MyExampleProvider 中实现 updateFieldByExample 方法:

public static String updateFieldByExample(ProviderContext providerContext) {
  return SqlScript.caching(providerContext, new SqlScript() {
    @Override
    public String getSql(EntityTable entity) {
      return ifTest("example.startSql != null and example.startSql != ''", () -> "${example.startSql}")
          + "UPDATE " + entity.table()
          + set(() -> "${column.toColumn()} = #{value}")
          + variableNotNull("example", "Example cannot be null")
          //是否允许空条件,默认允许,允许时不检查查询条件
          + (entity.getProp("updateByExample.allowEmpty", true) ?
          "" : variableIsFalse("example.isEmpty()", "Example Criteria cannot be empty"))
          + ExampleProvider.UPDATE_BY_EXAMPLE_WHERE_CLAUSE
          + ifTest("example.endSql != null and example.endSql != ''", () -> "${example.endSql}");
    }
  });
}

你可以对比上面的方法和 在 ExampleProvider 中的 updateByExample 方法,就是简单改了改 set() 中的内容,参考现有功能扩展很容易,理解里面写法后可以更灵活。

用法:

@Test
public void testUpdateByExample() {
  SqlSession sqlSession = getSqlSession();
  try {
    ExampleMapper<User, Example<User>> exampleMapper = sqlSession.getMapper(UserMapper.class);
    Example<User> example = new Example();
    example.createCriteria().andEqualTo(User::getId, 1L);
    //userName字段对应的列为name
    Assert.assertEquals(1, exampleMapper.updateFieldByExample(User::getUserName, "测试", example));
    sqlSession.rollback();
  } finally {
    //不要忘记关闭sqlSession
    sqlSession.close();
  }
}

执行的SQL:

DEBUG [main] - ==>  Preparing: UPDATE user SET name = ? WHERE ( id = ? )
DEBUG [main] - ==> Parameters: 测试(String), 1(Long)
DEBUG [main] - <==    Updates: 1

这个实现确实可以解决更新单个值的情况,多个值就不适用了,我自己实现了个example.updateEqualTo,但是需要修改Example源码,

public static String updateByExample(ProviderContext providerContext) {
        return SqlScript.caching(providerContext, new SqlScript() {
            @Override
            public String getSql(EntityTable entity) {
                return ifTest("example.startSql != null and example.startSql != ''", () -> "${example.startSql}")
                        + "UPDATE " + entity.table()
                        + set(() -> choose(() -> whenTest("example.updateCriterion != null and example.updateCriterion.size()>0", () -> UPDATE_BY_EXAMPLE_COLUMN)
                        + otherwise(() -> entity.updateColumns().stream().map(column -> column.columnEqualsProperty("entity.")).collect(Collectors.joining(",")))))
                        //TODO 测试
                        + variableNotNull("example", "Example cannot be null")
                        //是否允许空条件,默认允许,允许时不检查查询条件
                        + (entity.getProp("updateByExample.allowEmpty", true) ?
                        "" : variableIsFalse("example.isEmpty()", "Example Criteria cannot be empty"))
                        + UPDATE_BY_EXAMPLE_WHERE_CLAUSE
                        + ifTest("example.endSql != null and example.endSql != ''", () -> "${example.endSql}");
            }
        });
    }

public static final String UPDATE_BY_EXAMPLE_COLUMN =
            "  <foreach collection=\"example.updateCriterion\" item=\"criterion\"\n separator=\" , \">\n" +
                    "     ${criterion.condition} #{criterion.value}\n" +
                    "  </foreach>\n";

example = userAutoMapper.example();
            example.updateEqualTo(UserAuto::getName, "测试名称").updateEqualTo(UserAuto::getId, 1).createCriteria().andEqualTo(UserAuto::getId, userAuto.getId());
            Assert.assertEquals(1, userAutoMapper.updateByExample(null, example));
li362692680 commented 2 years ago

有什么好办法可以在不改源码的情况下实现我这个逻辑么??

abel533 commented 2 years ago

多个值直接用实体。

abel533 commented 2 years ago

即将发布的新版本可以更好的支持这个方法。

wrapper用法:

mapper.wrapper()
          .set(User::getUserName, "弓长无忌")
          .set(User::getSex, "M")
          .eq(User::getUserName, "张无忌").update()

example用法:

Example<User> example = new Example();
example.createCriteria().andEqualTo(User::getId, 1L);
example.set(User::getUserName, "男主角").set(User::getSex, "M");

exampleMapper.updateByExampleSetValues(example);