【MyBatis Plus】深入探索 MyBatis Plus 的条件构造器,自定义 SQL语句,Service 接口的实现
文章目录
- 前言
- 一、条件构造器
- 1.1 什么是条件构造器
- 1.2 QueryWrapper
- 1.3 UpdateWrapper
- 1.4 LambdaWrapper
- 二、自定义 SQL 语句
- 2.1 自定义 SQL 的基本用法
- 2.2 自定义 SQL 实现多表查询
- 三、Service 接口
- 3.1 对 Service 接口的认识
- 3.2 实现 Service 接口
- 3.3 实现增删改查功能
- 3.4 Lambda 功能
前言
在前文中,介绍了 MyBatis Plus 的一些基础功能,我们发现使用MyBatis Plus 可以非常简单的就实现对单表的增删改查操作。但是这些操作对应的 SQL 语句都非常简单,如果是面对一些复杂的 SQL 或者多表查询这样的情况,就需要我们自己来重新组织 SQL 语句了。
本文将重点探索 MyBatis Plus 的条件构造器、自定义SQL语句来解决这些复杂情况,然后实现 MyBatis Plus 提供的通用的 Service 接口,以展示如何在项目中使用 MyBatis Plus。
一、条件构造器
1.1 什么是条件构造器
在数据库中,除了新增操作之外,修改、删除、查询的 SQL 语句中都需要指定 where
条件。在 BaseMapper
中提供的相关的方法除了提供以 id
作为 where
的条件之外,也都支持更加复杂的 where
条件。
我们可以看到,在上面的BaseMapper
这些方法中,有很多方法都有一个 Wrapper
参数,而这个 Wrapper
就是条件构造器的抽象类,这个类有许多的实现类:
这些类就是条件构造器,它是 MyBatis Plus 中的一种查询条件封装方式,用于构建SQL语句中的WHERE条件部分。这些构造器的主要作用是根据给定的条件构建数据库操作的条件语句,以实现查询、更新、删除等操作。
其中,Wrapper
的子类 AbstractWrapper
提供了where
中包含的所有条件构造方法:
而QueryWrapper
在AbstractWrapper
的基础上拓展了一个select
方法,允许指定查询字段:
而UpdateWrapper
在AbstractWrapper
的基础上拓展了一个set
方法,允许指定SQL中的SET
部分:
另外,我们还发现有一组与Lambda
相关的条件构造器,为什么会存在这组构造器将在下文进行说明。
下面,让我来演示一下其中一些构造器主要的用法。
1.2 QueryWrapper
QueryWrapper
顾名思义,就是用于构建查询条件的WHERE子句的条件构造器。它提供了多种方法来构建条件,包括等值条件、范围条件、模糊查询等。
示例一:现在要求查询出
user
表中名字带o
,并且存款不少于 1000 的用户:
@Test
void testQueryMapper() {// 1. 构造查询条件 where name like "%o%" and balance >= 1000QueryWrapper<User> wrapper = new QueryWrapper<User>().select("id", "username", "info", "balance").like("username", "o").ge("balance", 1000);// 2. 根据 where 条件进行查询List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);
}
以下是对上述代码示例的说明:
-
在此代码中,我们首先创建了一个
QueryWrapper<User>
对象,用于构建查询条件的WHERE
子句。然后,通过链式方法调用来定义查询条件:-
.select("id", "username", "info", "balance")
:指定了要选择的列,包括id
、username
、info
和balance
。 -
.like("username", "o")
:设置了查询条件,要查找用户名中包含字母’o’的记录,类似于SQL中的WHERE username LIKE '%o%'
。 -
.ge("balance", 1000)
:设置了查询条件,筛选出余额大于或等于1000的记录。
-
-
接下来,使用
userMapper
执行查询操作,通过调用selectList(wrapper)
方法执行带有指定条件的SQL查询。
运行结果:
查询的结果被存储在一个List<User>
对象中,然后通过users.forEach(System.out::println)
遍历列表,将每个用户的详细信息打印到控制台。执行代码后,运行结果如下所示:
上述示例演示了如何使用QueryWrapper
构建查询条件以检索数据库表中符合特定条件的记录。这对于执行复杂的查询操作非常有用。
另外,QueryWrapper
也可以用于 Update
操作。
示例二:使用
QueryWrapper
进行update
操作,更新用户名为Jack
的用户的余额为 1000:
@Test
void testUpdateByQueryWrapper() {// 1. 构造查询条件:where name = "Jack"QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack");// 2. 准备更新数据,user 对象中的非空数据会作为 SET 的内容User user = new User();user.setBalance(1000);// 3. 更新userMapper.update(user, wrapper);
}
对上述代码的说明:
-
首先,构建了一个
QueryWrapper<User>
对象,用于构建查询条件。在这个例子中,使用了.eq("username", "Jack")
来设置查询条件,即要查找用户名为Jack
的记录。这相当于SQL中的WHERE username = 'Jack'
。 -
接下来,准备要更新的数据。创建一个
User
对象,并设置了要更新的字段,其中user.setBalance(1000)
将余额设置为1000。在user
对象中,非空数据会作为UPDATE
语句的SET
部分内容。 -
最后,使用
userMapper.update(user, wrapper)
来执行更新操作。这将更新满足QueryWrapper
条件的记录,将username
为Jack
的用户的余额更新为1000。
运行结果
执行代码后,运行结果如下所示:
上述示例演示了如何使用QueryWrapper
构建查询条件并执行更新操作,将指定条件的记录进行更新。这是一个示例,演示了使用QueryWrapper
来进行数据库更新操作。
1.3 UpdateWrapper
UpdateWrapper
是用于构建更新操作的WHERE
条件构造器。它允许设置更新的字段和更新的条件。以下是一些UpdateWrapper
的主要用法示例:
示例:更新
id
为 1, 2, 4 的用户的余额,每个人的余额都扣 200:
@Test
void testUpdateQuery() {// 1. 准备查询条件:where id in (1, 2, 4)UpdateWrapper<User> wrapper = new UpdateWrapper<User>().setSql("balance = balance - 200").in("id", Arrays.asList(1, 2, 4));// 2. 更新,注意此时 update 的第一个参数为 nulluserMapper.update(null, wrapper);
}
对上述代码的说明:
-
首先,创建了一个
UpdateWrapper<User>
对象,用于构建更新操作的WHERE
条件。在这个例子中,我们使用.in("id", Arrays.asList(1, 2, 4))
来设置查询条件,即要更新id
在1、2、4范围内的记录。这相当于SQL中的WHERE id IN (1, 2, 4)
。 -
接下来,使用
.setSql("balance = balance - 200")
来设置更新操作的内容。这表示要将balance
字段减少200。这相当于SQL中的SET balance = balance - 200
。 -
最后,使用
userMapper.update(null, wrapper)
来执行更新操作。需要注意的是,在这里update
方法的第一个参数为null
,因为在UpdateWrapper
中已经设置了更新的条件和内容。
执行代码后,运行结果如下所示:
上述示例演示了如何使用UpdateWrapper
构建更新操作的WHERE
条件,并执行更新操作,以减少满足条件的记录的余额。这是一个示例,演示了使用UpdateWrapper
进行数据库更新操作。
1.4 LambdaWrapper
通过上面的几个示例可以发现,无论是QueryWrapper
还是UpdateWrapper
在构造条件的时候都出现了硬编码,导致使用了字符串魔法值。这在编程规范中显然是不推荐的。那怎么样才能不写字段名,又能知道字段名呢?
其中一种办法是基于变量的gettter
方法结合反射技术。因此我们只要将条件对应的字段的getter
方法传递给条件构造器,就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用和Lambda
表达式。因此Mybatis Plus又提供了一套基于 Lambda
的Wrapper
,包含两个类:
LambdaQueryWrapper
LambdaUpdateWrapper
分别对应QueryWrapper
和UpdateWrapper
。、
下面以 LambdaQueryWrapper
为例,演示如何使用Lambda
条件构造器进行上文的查询操作。
@Testvoid testLambdaQueryMapper() {// 1. 准备查询条件:where name like "%o%" and balance >= 1000LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().select(User::getId, User::getUsername, User::getInfo, User::getBalance).like(User::getUsername, "o").ge(User::getBalance, 1000);// 2. 查询List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);}
对上述代码的说明:
-
创建了一个
LambdaQueryWrapper<User>
对象,用于构建查询条件。在这个例子中,使用了Lambda
表达式,通过User::getXXX
方式来引用实体类User
的字段,而不需要写字段名的字符串。这大大提高了代码的可读性和可维护性。 -
设置了查询条件,使用
like
方法进行模糊查询,匹配username
字段中包含字母"o"的记录,使用ge
方法筛选balance
字段大于或等于1000的记录。同时,通过select
方法指定了查询结果中的字段。 -
最后,使用
userMapper.selectList(wrapper)
执行查询操作,将满足条件的记录存储在users
列表中,并使用forEach
遍历并打印每个用户的详细信息。
运行代码后,运行结果如下所示:
这个示例演示了如何使用LambdaQueryWrapper
构建查询条件,不需要硬编码字段名,而是使用Lambda表达式引用实体类的字段,提高了代码的可读性和可维护性。
二、自定义 SQL 语句
在上面的演示 UpdateWrapper 的例子中,直接在代码中编写了更新的 SQL 语句:
这种写法在实际开发中一般都是不允许的,因为 SQL 语句最好都维护在持久层,而不是业务层。就从这个案例来说,由于条件是in
语句,只能将SQL
写在Mapper.xml
文件,利用foreach
来生成动态SQL
。这实在是太麻烦了,假如查询条件更复杂,动态SQL的编写也会更加复杂。
所以,Mybatis Plus 提供了自定义 SQL 功能,可以让我们利用 Wrapper
生成查询条件,再结合Mapper.xml
编写 SQL 语句。
2.1 自定义 SQL 的基本用法
对于上面的例子,我们可以改写成自定义 SQL 的形式。
首先,在UserMapper
接口中新增一个方法updateBalanceByIds
:
public interface UserMapper extends BaseMapper<User> {void updateBalanceByIds(@Param("ew") QueryWrapper<User> wrapper, @Param("money") int money);
}
然后,在 UserMapper.xml
中编写该方法对应的 SQL 语句:
<update id="updateBalanceByIds">UPDATE user SET balance = balance - ${money} ${ew.customSqlSegment}
</update>
注意,最后使用${ew.customSqlSegment}
拼接由 Wrapper
构造的查询条件。
然后,创建一个单元测试方法testCustomSQLForUpdate
来测试我们的自定义SQL是否生效:
@Test
void testCustomSQLForUpdate() {// 1. 要扣减的金额int amount = 200;// 2. 准备查询条件QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", Arrays.asList(1, 2, 4));// 3. 执行更新userMapper.updateBalanceByIds(wrapper, amount);
}
运行结果:
2.2 自定义 SQL 实现多表查询
MyBatis Plus 本身不支持多表查询,但我们可以使用 Wrapper 来自定义多表查询的条件以达到多表查询的效果。例如,现在有一个地址表,其结构如下:
通过用户id
与之关联,现在要查询出地址在北京,并且用户id
在 1,2,4 之间的用户,如果使用 MyBatis,要编写的SQL语句如下:
<select id="queryUserByIdAndAddr" resultType="com.demo.mp.domain.po.User">SELECT *FROM user uINNER JOIN address a ON u.id = a.user_idWHERE u.id<foreach collection="ids" separator="," item="id" open="IN (" close=")">#{id}</foreach>AND a.city = #{city}</select>
可以看出其中最复杂的就是WHERE
条件的编写,如果业务复杂一些,这里的SQL会更加复杂。但是基于自定义 SQL 结合 Wrapper
的写法,我们就可以利用 Wrapper
来构建查询条件,然后手写SELECT
及FROM
部分,实现多表查询。
首先,查询条件可以像下面这样:
@Test
void testCustomJoinAddress() {// 1. 准备查询条件QueryWrapper<User> wrapper = new QueryWrapper<User>().in("u.id", Arrays.asList(1, 2, 4)).eq("a.city", "北京");// 2. 调用自定义方法List<User> users = userMapper.queryUserByIdAndAddr(wrapper);users.forEach(System.out::println);
}
其中,u
代表user
表,a
代表 address
表。
然后,在UserMapper
接口中实现queryUserByIdAndAddr
方法:
@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByIdAndAddr(@Param("ew") QueryWrapper<User> wrapper);
注意,此时可以直接将SQL语句以注解的方式写在对应的方法上面。当然,也可以在UserMapper.xml
中写 SQL:
<select id="queryUserByIdAndAddr" resultType="com.demo.mp.domain.po.User">SELECT * FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}
</select>
三、Service 接口
3.1 对 Service 接口的认识
Mybatis Plus 不仅提供了 BaseMapper
接口,还提供了通用的Service
层接口及默认实现,封装了一些常用的service
模板方法。
其中通用接口为IService
,默认实现为ServiceImpl
,其中封装的方法可以分为以下几类:
save
:新增remove
:删除update
:更新get
:查询单个结果list
:查询集合结果count
:计数page
:分页查询
首先让我们来看看 IService
接口中增删改查相关功能的方法:
新增:
save
是新增单个元素saveBatch
是批量新增saveOrUpdate
是根据id
判断,如果数据存在就更新,不存在则新增saveOrUpdateBatch
是批量的新增或修改
删除:
removeById
:根据id
删除removeByIds
:根据id
批量删除removeByMap
:根据Map
中的键值对为条件删除remove(Wrapper<T>)
:根据Wrapper条件删除
修改:
updateById
:根据id
修改update(Wrapper<T>)
:根据UpdateWrapper
修改,Wrapper
中包含set
和where
部分update(T,Wrapper<T>)
:按照T
内的数据修改与Wrapper
匹配到的数据updateBatchById
:根据id
批量修改
Get
相关方法:
getById
:根据id
查询1条数据getOne(Wrapper<T>)
:根据Wrapper
查询1条数据getBaseMapper
:获取Service
内的BaseMapper
实现,某些时候需要直接调用Mapper
内的自定义SQL
时可以用这个方法获取到Mapper
获取 List
相关方法:
listByIds
:根据id
批量查询list(Wrapper<T>)
:根据Wrapper条件查询多条数据list()
:查询所有
Count
计数相关方法:
count()
:统计所有数量count(Wrapper<T>)
:统计符合Wrapper条件的数据数量
3.2 实现 Service 接口
由于Service
层中经常需要定义与业务有关的自定义方法,因此我们不能直接使用IService
,而是自定义Service
接口,然后继承IService
以拓展方法。同时,让自定义的Service
实现类继承ServiceImpl
,这样就不用自己实现IService
中的接口了。
首先,定义IUserService
,继承IService
接口:
public interface IUserService extends IService<User> {
}
然后,编写UserServiceImpl
类,继承ServiceImpl
类,实现UserService
接口:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
最终,Service
的目录结构如下:
3.3 实现增删改查功能
上面已经实现了Service
层的功能,接下来我们就能够快速实现下面 4 个接口:
编号 | 接口 | 请求方式 | 请求路径 | 请求参数 | 返回值 |
---|---|---|---|---|---|
1 | 新增用户 | POST | /users | 用户表单实体 | 无 |
2 | 删除用户 | DELETE | /users/{id} | 用户id | 无 |
3 | 根据id查询用户 | GET | /users/{id} | 用户id | 用户 VO |
4 | 根据id批量查询 | GET | /users | 用户id集合 | 用户 VO 集合 |
首先,需要为这些接口实现两个实体类:
UserFormDTO
:代表新增时的用户表单;UserVO
:代表查询的返回结果。
首先是UserFormDTO
:
@Data
@ApiModel(description = "用户表单实体")
public class UserFormDTO {@ApiModelProperty("id")private Long id;@ApiModelProperty("用户名")private String username;@ApiModelProperty("密码")private String password;@ApiModelProperty("注册手机号")private String phone;@ApiModelProperty("详细信息,JSON风格")private String info;@ApiModelProperty("账户余额")private Integer balance;
}
然后是UserVO
:
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {@ApiModelProperty("用户id")private Long id;@ApiModelProperty("用户名")private String username;@ApiModelProperty("详细信息")private String info;@ApiModelProperty("使用状态(1正常 2冻结)")private Integer status;@ApiModelProperty("账户余额")private Integer balance;
}
最后,按照RESTFul
风格编写Controller
接口方法:
@Api(tags = "用户管理接口")
@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate IUserService userService;@PostMapping@ApiOperation("新增用户接口")public void saveUser(@RequestBody UserFormDTO userFormDTO){// 1. 将 DTO 转为 POUser user = BeanUtil.copyProperties(userFormDTO, User.class);// 2. 保存userService.save(user);}@DeleteMapping("/{id}")@ApiOperation("根据id删除用户接口")public void removeUserById(@PathVariable("id") Long id){userService.removeById(id);}@GetMapping("/{id}")@ApiOperation("根据id查询用户接口")public UserVO queryUserById(@PathVariable("id") Long id){// 1. 查询 PO 对象User user = userService.getById(id);// 2. 将 PO 转换为 VO 并返回return BeanUtil.copyProperties(user, UserVO.class);}@GetMapping@ApiOperation("根据id批量查询用户接口")public List<UserVO> queryUserByIds(@RequestParam("ids") List<Long> ids){// 1. 查询 PO 对象集合List<User> users = userService.listByIds(ids);// 2. 将 PO 转换为 VO 并返回return BeanUtil.copyToList(users, UserVO.class);}
}
可以看到上述接口都直接在 controller
即可实现,无需编写任何service
代码,非常方便。
通过上面的controller
类可以发现实现单表简单的增删改查操作非常简单,不过,一些带有业务逻辑的接口则需要在service
中自定义实现了。
例如这个需求: 根据
id
扣减用户余额。
这看起来是个简单修改功能,只要修改用户余额即可。但这个业务包含一些业务逻辑处理:
- 判断用户状态是否正常;
- 判断用户余额是否充足。
这些业务逻辑都要在service
层来做,另外更新余额需要自定义 SQL,要在mapper
中来实现。因此,我们除了要编写controller
以外,具体的业务还要在service
和mapper
中编写。
- 首先在
UserController
中定义一个方法:
@PostMapping("{id}/deduct/{money}")
@ApiOperation("根据id扣减用户余额")
public void deductBalanceById(@PathVariable("id") Long id, @PathVariable("money") Integer money){userService.deductBalanceById(id, money);
}
- 然后是
UserService
接口:
public interface IUserService extends IService<User> {void deductBalanceById(Long id, Integer money);
}
- 再然后是
UserServiceImp
l实现类:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic void deductBalanceById(Long id, Integer money) {// 1. 查询用户User user = getById(id);// 2. 判断用户状态if(user == null || user.getStatus() == 2){throw new RuntimeException("用户状态异常!");}// 3. 判断用户余额if(user.getBalance() < money){throw new RuntimeException("用户余额不足!");}// 4. 扣减余额baseMapper.deductBalanceById(id, money);}
}
- 最后是
Mapper
:
public interface UserMapper extends BaseMapper<User> {@Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}")void deductBalanceById(@Param("id") Long id, @Param("money") Integer money);
}
3.4 Lambda 功能
IService
接口中也提供了Lambda
功能来简化我们的复杂查询及更新功能。让我们通过下面两个案例来了解一下如何使用这个 Lambda
功能。
案例一:使用
lambdaUpdate
改写上述扣减余额的功能
此时,我们只需要修改 UserServiceImpl
中deductBalanceById
方法的实现代码即可:
@Override
public void deductBalanceById(Long id, Integer money) {// 1. 查询用户User user = getById(id);// 2. 判断用户状态if(user == null || user.getStatus() == 2){throw new RuntimeException("用户状态异常!");}// 3. 判断用户余额if(user.getBalance() < money){throw new RuntimeException("用户余额不足!");}// 4. 扣减余额int remainBalance = user.getBalance() - money;lambdaUpdate().set(User::getBalance, remainBalance).set(remainBalance == 0, User::getStatus, 2) // 如果用户余额为 0 就冻结.eq(User::getId, id).eq(User::getBalance, user.getBalance()) // CAS 乐观锁判断此期间用户余额是否被修改.update();
}
此时可以直接使用 lambdaUpdate
来构造我们的SQL语句,并在最后调用 update
执行这些SQL语句。对于上述代码有以下值得注意的地方:
- 当用户的余额为 0 的时候,就冻结该用户,即改变
status
字段。注意,set
方法的第一个参数是一个条件判断语句,如果该条件为真,则在SQL执行该SET
操作; - 在执行
update
操作之前,使用乐观锁 CAS 来判断余额是否被修改,保证在并发时的线程安全问题。
案例二:使用
lambdaQuery
实现一个根据复杂条件查询用户的接口,查询条件如下:
- name:用户名关键字,可以为空
- status:用户状态,可以为空
- minBalance:最小余额,可以为空
- maxBalance:最大余额,可以为空
可以将上述需求理解成一个用户的后台管理界面,管理员可以自己选择条件来筛选用户,因此上述条件不一定存在,需要做判断。
此时,由于前端的参数传入的可能性比较多,因此就直接使用一个 UserQuery
的实体来进行接收:
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {@ApiModelProperty("用户名关键字")private String name;@ApiModelProperty("用户状态:1-正常,2-冻结")private Integer status;@ApiModelProperty("余额最小值")private Integer minBalance;@ApiModelProperty("余额最大值")private Integer maxBalance;
}
Controller
方法:
@GetMapping("/list")
@ApiOperation("根据复杂条件查询用户集合接口")
public List<UserVO> queryUsers(UserQuery userQuery){// 1. 查询 PO 对象集合List<User> users = userService.queryUsers(userQuery);// 2. 将 PO 转换为 VO 并返回return BeanUtil.copyToList(users, UserVO.class);
}
IUserService
接口:
public interface IUserService extends IService<User> {List<User> queryUsers(UserQuery userQuery);
}
UserServiceImpl
实现类:
@Override
public List<User> queryUsers(UserQuery userQuery) {String name = userQuery.getName();Integer status = userQuery.getStatus();Integer minBalance = userQuery.getMinBalance();Integer maxBalance = userQuery.getMaxBalance();return lambdaQuery().like(name != null, User::getUsername, name).eq(status != null, User::getStatus, status).ge(minBalance != null, User::getBalance, minBalance).le(maxBalance != null, User::getBalance, maxBalance).list();
}
在链式编程的最后添加一个list()
,这是在告诉 MyBatis Plus 我们的调用结果需要是一个list
集合。这里不仅可以用list()
,可选的方法有:
.one()
:最多1个结果;.list()
:返回集合结果;.count()
:返回计数结果。
Mybatis Plus会根据链式编程的最后一个方法来判断最终的返回结果。
相关文章:

【MyBatis Plus】深入探索 MyBatis Plus 的条件构造器,自定义 SQL语句,Service 接口的实现
文章目录 前言一、条件构造器1.1 什么是条件构造器1.2 QueryWrapper1.3 UpdateWrapper1.4 LambdaWrapper 二、自定义 SQL 语句2.1 自定义 SQL 的基本用法2.2 自定义 SQL 实现多表查询 三、Service 接口3.1 对 Service 接口的认识3.2 实现 Service 接口3.3 实现增删改查功能3.4 …...

基于AI与物联网技术的智能视频监控系统架构剖析
智能视频监控系统正逐渐成为我们日常生活和工作中不可或缺的一部分。基于物联网的智能监控系统架构为我们在各个领域提供了更高效、智能化和安全的监控解决方案。本文将以旭帆科技EasyCVR视频监控云平台为例,介绍基于AI、物联网的智能监控系统的架构,并探…...

mysql 基础知识
MySQL 是一种关系型数据库,在Java企业级开发中非常常用,因为 MySQL 是开源免费的,并且方便扩展。阿里巴巴数据库系统也大量用到了 MySQL,因此它的稳定性是有保障的。MySQL是开放源代码的,因此任何人都可以在 GPL(Gener…...

Flink CDC 2.0 主要是借鉴 DBLog 算法
DBLog 算法原理 DBLog 这个算法的原理分成两个部分,第一部分是分 chunk,第二部分是读 chunk。分 chunk 就是把一张表分为多个 chunk(桶/片)。我可以把这些 chunk 分发给不同的并发的 task 去做。例如:有 reader1 和 re…...

win10 + VS2017 编译libjpeg(jpeg-9b)--更新
刚刚写了一篇“win10 VS2017 编译libjpeg(jpeg-9b)”, 然后就发现,还有一个更好的方法。因此,重新更新了一篇,作为对比与参考。 需要用到的文件: jpeg-9b.zip win32.mak 下载链接链接…...

使用pycharm远程调试
使用pycharm 专业版, 在设置解释器中,具备ssh 解释器功能; 一般在本地无法调试远程端代码,机械性的scp传输文件十分影响工作效率,PyCharm的Pro支持远程Run,Debug,等可视化的功能。 操作系统&…...

rust学习
rust学习 String类型clone和copy结构体的内存分布for循环(<font color red>important!)堆和栈数据结构vector panic失败就 panic: unwrap 和 expect传播错误 模式匹配忽略模式的值绑定 泛型特征Trait定义特征为类型实现特征孤儿规则使…...

GCC、g++、gcc的关系
GCC、g、gcc的关系 引言 VsCode中对编译环境进行配置的时选择编译器时发现有多种不同的编译器 GNU计划和GCC GNU的全称 GNU’s Not UNIX GNU是一个计划 Q:为什么会有这个计划 因为当时的Unix开始收费和商业闭源,有人觉得不爽→ 想要自己开发和Unix类似的→GNU计划 GUN计划目…...

IP应用场景API的反欺诈潜力:保护在线市场不受欺诈行为侵害
前言 在数字化时代,网络上的商业活动迅速增长,但与之同时,欺诈行为也在不断演化。欺诈者不断寻找新方法来窃取个人信息、进行金融欺诈以及实施其他不法行为。为了应对这一威胁,企业和组织需要强大的工具,以识别和防止…...

常用的主流音乐编曲软件有哪些?
FL Studio是一款备受音乐人喜爱的超强编曲软件。最新的FL Studio版本将所有音频形式都视为采样,使得它在各个领域都有出色的表现。该软件操作简单,界面友好,非常适合新手全面学习和使用。此外,FL Studio完美支持Windows和Mac操作系…...

面试题:为什么HashMap 使用的时候指定容量?
文章目录 前言正文为什么要指定容量? 前言 其实可以看到我写了这么久的博客,很少去写hashMap的东西。 为什么?因为这个东西感觉是java面试必备的,我感觉大家都看到腻了,所以一直没怎么去写hashMap相关的。 本篇内容&…...

基于C/C++的UG二次开发流程
文章目录 基于C/C的UG二次开发流程1 环境搭建1.1 新建工程1.2 项目属性设置1.3 添加入口函数并生成dll文件1.4 执行程序1.5 ufsta入口1.5.1 创建程序部署目录结构1.5.2 创建菜单文件1.5.3 设置系统环境变量1.5.4 制作对话框1.5.5 创建代码1.5.6 部署和执行 基于C/C的UG二次开发…...

“第五十二天”
算术逻辑单元: 之前提过的运算器包括MQ,ACC,ALU,X,PSW;运算器可以实现运算以及一些辅助功能(移位,求补等)。 其中ALU负责运算,运算包括算术运算(加减乘除等)和逻辑运算(…...

Lvs+Nginx+NDS
什么是?为什么?需要负载均衡 一个网站在创建初期,一般来说都是只有一台服务器对用户提供服务 从图里可以看出,用户经过互联网直接连接了后端服务器,如果这台服务器什么时候突然 GG 了,用户将无法访问这…...

JavaWeb——Servlet原理、生命周期、IDEA中实现一个Servlet(全过程)
6、servlet 6.1、什么是servlet 在JavaWeb中,Servlet是基于Java编写的服务器端组件,用于处理客户端(通常是Web浏览器)发送的HTTP请求并生成相应的HTTP响应。Servlet运行在Web服务器上,与Web容器(如Tomcat&…...

Android 12.0 ota升级之SettingsProvider新增和修改系统数据相关功能实现
1. 前言 在12.0的系统rom定制化开发中,在解决一些已经上线的bug后,进行ota升级的过程中,由于在SettingsProvider中新增了系统属性和修改某项系统属性值,但是在ota升级以后发现没有 更新,需要恢复出厂设置以后才会更改,但是恢复出厂设置 会丢掉一些数据,这是应为系统数据…...

python---for循环结构中的else结构(是同级关系)
为什么需要在for循环中添加else结构 循环可以和else配合使用, else下方缩进的代码指的是当循环正常结束之后要执行的代码。 强调: 循环 正常结束,else之后要执行的代码。 非正常结束,其else中的代码是不会执行的。…...

XLua中lua读写cs对象的原理
LuaCallCS 1. 传递C#对象到Lua XLua在C#维护了两个数据结构,ObjectPool和ReverseMap。 首次传递一个C#对象obj到Lua时,对象被加入到ObjectPool中,并为它创建一个唯一标识objId,建立obj和objId的双向映射。 ObjectPool: objId-…...

新手小白怎么选择配音软件?
现在的配音软件软件很多,各种类型的都比较多,对于新手小白来说不知该如何选择,今天就来给你分享几款好用的配音软件。不论是制作短视频还是制作平常音频都完全可以。 第一款:悦音配音 这是一款专业的视频配音软件,多端…...

linux查看硬件信息命令
文章目录 cpu内核版本内存硬盘主板服务器参考链接 cpu cat /proc/cpuinfo 一个物理CPU可以有1个或者多个物理内核,一个物理内核可以作为1个或者2个逻辑CPU。 物理CPU数就是主板上实际插入的CPU数量。 在Linux上cat /proc/cpuinfo,会打印每个cpu的信息 …...

TSINGSEE青犀省级高速公路视频上云联网方案:全面实现联网化、共享化、智能化
一、需求背景 随着高速铁路的建设及铁路管理的精细化,原有的模拟安防视频监控系统已经不能满足视频监控需求,越来越多站点在建设时已开始规划高清安防视频监控系统。高速公路视频监控资源非常丰富,需要对其进行综合管理与利用。通过构建监控…...

知识图谱相关的操作
微软生成自己的图谱:GitHub - microsoft/SmartKG: This project accepts excel files as input which contains the description of a Knowledge Graph (Vertexes and Edges) and convert it into an in-memory Graph Store. This project implements APIs to searc…...

【Javascript】json
目录 什么是json? 书写格式 json 序列化和反序列化 序列化 反序列化 什么是json? JSON(JavaScript Object Notation)是⼀种轻量级的数据交换格式,它基于JavaScript的⼀个⼦集,易于⼈的编写和阅读,也易于机器解析…...

零资源的大语言模型幻觉预防
零资源的大语言模型幻觉预防 摘要1 引言2 相关工作2.1 幻觉检测和纠正方法2.2 幻觉检测数据集 3 方法论3.1 概念提取3.2 概念猜测3.2.1 概念解释3.2.2 概念推理 3.3 聚合3.3.1 概念频率分数3.3.2 加权聚合 4 实验5 总结 摘要 大语言模型(LLMs)在各个领域…...

智能终端界面自动化测试操作工具 - Appium常见用法
1. Appium 是什么可以做什么? Appium 是一款开源的移动应用自动化测试框架,用于测试移动应用程序的功能和用户界面。它支持多种移动平台,包括 Android 和 iOS,可以使用多种编程语言进行脚本编写,如 Python、Java、Jav…...

结构体数组经典运用---选票系统
结构体的引入 1、概念:结构体和其他类型基础数据类型一样,例如int类型,char类型,float类型等。整型数,浮点型数,字符串是分散的数据表示,有时候我们需要用很多类型的数据来表示一个整体&#x…...

code too large
描述:比较尴尬,一个方法的代码接近10000行了,部署服务器的时候提示(java :code[255,21] too large),提示代码过长,无法运行。 查看了一下百度:解决的思路 JVM规范:「类或接口可以声明的字段数量限制在 655…...

vue中把弹出层.vue文件注册成组件供其他.vue文件调用的写法
背景:因弹出层多个页面的详情都是一样的,因此把弹出层定义成组件,多次调用 定义组件的过程中出现很多问题,因此再次记录最终成功的写法 一、 简单实现页面调用弹出层组件的打开弹出层方法: 1. 弹出层组件 (in…...

mac 查看GPU使用
首先搜索活动监视器 然后 点击窗口->gpu历史记录 记住不是立马出结果,而是 需要等半分钟左右的...

工业4.0的安全挑战与解决方案
在当今数字化时代,工业4.0已经成为制造业的核心趋势。工业4.0的兴起为生产企业带来了前所未有的效率和灵活性,但与之伴随而来的是一系列的安全挑战。本文将深入探讨工业4.0的安全挑战,并提供一些解决方案,以确保制造业的数字化转型…...