目录
3、 在application.yml文件中配置数据源(数据库连接信息):
一、MyBatis回顾
(1)什么是MyBatis:MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。MyBatis-Plus其实就是对MyBatis的加强。
mybatisplus它针对mybatis只做增强,而你的mybatis原有的功能都能用
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '姓名',
`age` int(11) NULL DEFAULT NULL COMMENT '年龄',
`email` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '张三', 18, 'zhangsan@qq.com');
INSERT INTO `user` VALUES (2, '李四', 18, 'lisi@qq.com');
INSERT INTO `user` VALUES (3, '王五', 18, 'wangwu@qq.com');
INSERT INTO `user` VALUES (4, '赵六', 18, 'zhaoliu@qq.com');
INSERT INTO `user` VALUES (5, '二狗', 18, 'ergou@qq.com');
INSERT INTO `user` VALUES (6, '飞云', 18, 'feiyun@qq.com');
SET FOREIGN_KEY_CHECKS = 1;
二、什么是MyBatis-Plus
(1)MyBatis-Plus是MyBatis的增强,在MyBatis的基础上,只做增强而不做改变,是为了简化开发、提高效率而生。
(2)MyBatis-Plus所需要的依赖:
它是第三方提供的的,需要我们手工来加:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
三、创建SpringBoot项目:
1、初始化SpringBoot项目:
2、手工加入mybatisPlus依赖:
3、 在application.yml文件中配置数据源(数据库连接信息):
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver #其实不需要写,默认就是
#其实你连接本地数据库,并且端口号为3306的话,只需要这样写 jdbc:mysql:///mpdb,这是localhost的简写
url: jdbc:mysql://localhost:3306/mpdb?serverTimezone=UTC
username: root
password: 3333
#获取打印日志,填写主包,我项目里面所有的类都写在这个主包下:启动类放在主包之下,其他类放在主包的子包之下
logging:
level:
com.lifang.bootMybatispuls: debug
4、创建数据库表对应的实体类User:
创建实体类我们要习惯性的去序列化一下
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private Integer id;
private String name;
private Integer age;
private String email;
}
5、创建持久层DAO接口:
UserMapper 继承了BaseMapper<User>接口就自然而然就具备了增删改查的能力,对谁的增删改查呢?就是对这个泛型里面限定的这个实体类对应的表的增删改查的能力。
这个UserMapper 接口已经具备了对User这个实体类对应的表的增删改查的能力
public interface UserMapper extends BaseMapper<User> {
}
@MapperScan:扫描持久层DAO接口所在的包名,找到包中的所有DAO接口和对应的sql映射文件,创建DAO接口对应的代理对象
@MapperScan("com.lifang.bootMybatispuls.mapper")
@SpringBootApplication
public class Boot307MybatisPulsApplication {
public static void main(String[] args) {
SpringApplication.run(Boot307MybatisPulsApplication.class, args);
}
}
四、测试通用的Mapper接口:
现在这个mapper接口已经具备了对User这个实体类对应的表的增删改查的能力,现在我们来测试一下:
4.1、selectList:
@SpringBootTest
public class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
public void testSelectAll(){
List<User> users = userMapper.selectList(null);
users.forEach(user-> System.out.println(user));
}
}
我们没有写查询语句,这个sql语句就是mybatisplus通过扫描这个实体,它产生的查询语句:
4.2、insert:
@Test
public void testInsert(){
User user = new User(null,"花花",3,"huahau@qq.com");
int num = userMapper.insert(user);
System.out.println(num);
}
这个i d我给的是个null,但却插入了一个很长的值,这个实际上是因为它默认有一个雪花算法的策略,帮我们产生了一个id:-2090029054
如果说你想继续沿用id自增的话,你得把表结构改成自增的字段,然后去设修改一下策略:
4.3、delete:
@Test
public void testDelete(){
int num = userMapper.deleteById(-2090029054);
System.out.println(num);
}
@Test
public void testDelete(){
User user = new User(449347585,"sss","sss.com",2333);
int result = userMapper.deleteById(user);//其实本质就是根据这个id
System.out.println(result);
}
@Test
public void testDelete2(){
User user = new User(-2090029054,"花花1",5,"huahau@qq.com");
int num = userMapper.deleteById(user);//其实还是根据id删除,即使你其他信息写错了
System.out.println(num);
}
4.4、update:
@Test
public void testDelete(){
int num = userMapper.updateById(new User(666,"花花11",5,"huahau@qq.com"));
System.out.println(num);
}
五、常用的注解:
5.1、@TableName
5.1.1.作用:
①、表名注解,标识实体类对应的表,做实体类的类名和数据库中的表名的一个映射
②、没有用到注解的话,代表的就是默认情况下这个User实体类的类名跟我们数据库里的表名的是一致的
5.1.2.举例:
当实体类的类名和数据库的表名不一致时:
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo implements Serializable {
//创建实体类我们习惯性的去序列化一下
private Integer id;
private String name;
private String email;
private Integer age;
}
@SpringBootTest
class Boot08MybatisPlusApplicationTests {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
public void testTableName(){
UserInfo userInfo = new UserInfo(null,"李四","@qq.com",20);
int result = userInfoMapper.insert(userInfo);
System.out.println(result);
}
}
解决办法:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value="user")//去绑定它对应的数据库里的表名@TableName("user")
public class UserInfo implements Serializable {
//创建实体类我们习惯性的去序列化一下
private Integer id;
private String name;
private String email;
private Integer age;
}
5.2、@TableField
5.1.1.主要的三个作用:
①、value属性:
做实体类的属性名和表的字段名的一个映射,默认为实体类的属性名和表的字段名一致;
②、exist属性:
该属性是否为数据库表字段,默认存在;
如果这个属性在java里面要用,但是你表里不需要,那这个字段你就想办法给它忽略掉:用@TableField注解,让它等于false,把它忽略掉,这样它就表示该字段在表里不存在,去做增删改查的时候,它会忽略该字段:
③、select属性:
是否进行 select 查询;
默认查询的话,它会查到实体类中所有属性对应的字段,如果那么这个属性/字段的值我并不想让它出现在查询结果里面:就是我查询时不需要查到这个字段,那我们可以通过select属性=false,给它来做一个限定:select查询的时候,就不会去查这个字段,这个字段对应的属性值将被赋默认值。
5.1.2.三个属性代码举例:
①、value属性:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value="user")//去绑定它对应的数据库里的表名
public class UserInfo implements Serializable {
//创建实体类我们习惯性的去序列化一下
private Integer id;
private String name;
//它就会从数据库中查询email属性:
@TableField(value = "email")
private String mail;
private Integer age;
}
@Test
public void testTableField1(){
List<UserInfo> userInfos = userInfoMapper.selectList(null);
userInfos.forEach(info -> System.out.println(info));
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value="user")//去绑定它对应的数据库里的表名
public class UserInfo implements Serializable {
//创建实体类我们习惯性的去序列化一下
private Integer id;
private String name;
//它就会从数据库中查询email属性:
// @TableField(value = "email")
private String mail;
private Integer age;
}
②、exist属性:
由于开发的需要,我们可能会在实体类里面写一些别的属性。假设我User实体类中有个status状态属性,但表中并没有status字段,表中也不需要存在这个字段,现在如果去测试的话,他必然报错:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value="user")//去绑定它对应的数据库里的表名
public class UserInfo implements Serializable {
//创建实体类我们习惯性的去序列化一下
private Integer id;
private String name;
//它就会从数据库中查询email属性:
@TableField(value = "email")
private String mail;
private Integer age;
//@TableField(exist = false)
private Integer status;
}
@Test
public void testTableField2(){
List<UserInfo> userInfos = userInfoMapper.selectList(null);
userInfos.forEach(info -> System.out.println(info));
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value="user")//去绑定它对应的数据库里的表名
public class UserInfo implements Serializable {
//创建实体类我们习惯性的去序列化一下
private Integer id;
private String name;
//它就会从数据库中查询email属性:
@TableField(value = "email")
private String mail;
private Integer age;
//@TableField(exist = false)
private Integer status;
}
@Test
public void testTableField3(){
UserInfo info = new UserInfo(null,"张三","qq.com",23,1);
int result = userInfoMapper.insert(info);
System.out.println(result);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value="user")//去绑定它对应的数据库里的表名
public class UserInfo implements Serializable {
//创建实体类我们习惯性的去序列化一下
private Integer id;
private String name;
//它就会从数据库中查询email属性:
@TableField(value = "email")
private String mail;
private Integer age;
@TableField(exist = false)
private Integer status;
}
@Test
public void testTableName(){
UserInfo userInfo = new UserInfo(null,"李四","@qq.com",20,0);
int result = userInfoMapper.insert(userInfo);
System.out.println(result);
}
③、select属性:
默认查询的话,它会查到实体类中所有属性对应的字段,如果那么这个属性/字段的值我并不想让它出现在查询结果里面:就是我查询时不需要查到这个字段,那我们可以通过select属性=false,给它来做一个限定:select查询的时候,就不会去查这个字段,这个字段对应的属性值将被赋默认值。是执行查询语句的时候!
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value="user")//去绑定它对应的数据库里的表名
public class UserInfo implements Serializable {
//创建实体类我们习惯性的去序列化一下
private Integer id;
private String name;
//它就会从数据库中查询email属性:
@TableField(value = "email",select = false)
private String mail;
private Integer age;
@TableField(exist = false)
private Integer status;
}
@Test
public void testTableField1(){
List<UserInfo> userInfos = userInfoMapper.selectList(null);
userInfos.forEach(info -> System.out.println(info));
}
这是@TableField这个注解的三个基本用法,当然还有更多的属性,大家可以上官网更详细的表格去查阅它。
5.3、@TableId
如果我们对主键所对应的属性没有做任何配置的,它默认使用的是雪花算法:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value="user")//去绑定它对应的数据库里的表名
public class UserInfo implements Serializable {
//创建实体类我们习惯性的去序列化一下
//@TableId(type = IdType.AUTO)
private Integer id;
private String name;
//它就会从数据库中查询email属性:
@TableField(value = "email",select = false)
private String mail;
private Integer age;
@TableField(exist = false)
private Integer status;
}
雪花算法,注意这个数据前后两条数据它并不是顺序的,它不是按顺序来,你可以认为是随机的,它基于算法产生的一个随机数:
如何使用数据库自增:
①确定表中的主键字段为自增的:
②id上面加@TableId注解: @TableId(type = IdType.AUTO)
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value="user")//去绑定它对应的数据库里的表名
public class UserInfo implements Serializable {
//创建实体类我们习惯性的去序列化一下
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
//它就会从数据库中查询email属性:
@TableField(value = "email",select = false)
private String mail;
private Integer age;
@TableField(exist = false)
private Integer status;
}
在这里你可以修改以什么数字开始自增,但是设置的数字必须比你最大的id值要大:
5.4、@TableLogic
5.4.1.主要介绍:
逻辑删除的本质就是通过一个字段来标记这个数据是否有效,是否是删除这么一个状态。
mybatispuls为我们提供了@TableLogic这么一个注解。这个注解就可以帮我们去处理这些事情,让我们在后续的增删改查的操作里面,我们不需要去关注到底要不要加上删除的标记字段来过滤数据,也就是不需要考虑已经被逻辑删除的数据了,所以我们用上这个注解,可以降低我们代码的工作量。
万一我某个特别的需求,我需要查询所有的,包括已经删除数据,我都想查出来,那怎么办?那你就自己写一个sql语句,我们现在mybatisplus它针对mybatis只做增强,而你的mybatis原有的功能都能用,所以那种情况你就自己单独写个sql语句就好了。
比如说京东,我们在京东上下单买东西,会有交易记录,京东上有一个删除按钮。我们是可以把那个交易记录给它删除掉,但会不会真的删掉了呢?肯定不会。
因为如果你真的是从京东的数据库里面把它删掉了。未来你们俩如果有纠纷,这个数据到哪儿去找?所以对于任何公司的这种业务数据,它一般情况下都很难做物理删除。
那怎么做呢?一般来说啊会有两种方式,我接触过的两种方式。
一种就是我们现在要讲的逻辑删除。另外一种就是历史表,就是说我们为某某表创建一张历史表,我们把这个数据删掉了,确实从我们表里删掉了,然后把它挪到另外一张历史表,为什么要这样做呢?因为我们的那个主表里面的数据量可能会比较大。如果历史数据、你删掉的数据也放到里面,它会影响我们的查询效率。所以说我们又不想把它真的删掉,所以我们可以把这个数据给它移动到别的表里去,未来真的要查找的话,那我们也能找出来,又不影响我们主表的数据。
第二种逻辑删除就是在当前的这张表里,加一个字段来做一个标识,标识这个数据是未删除还是已删除的数据
所以逻辑删除它本质就是做一个标记。但是这种删除跟历史表的各有利弊,因为逻辑删除是在当前表里用一个字段来做标记,那意味着如果你这个表里有大量的数据删除,那么大量的这些删除的数据都存在这张表里,如果整个表的数据量比较大的话,到后期肯定会影响你的查询效率的。
所以它适合数据量并不是很大的情况。那我们用逻辑删除
5.4.2.使用步骤:
①、推荐使用全局的逻辑删除:
所有的表里面只要有逻辑删除,都采用统一的字段名。如果采用了统一的字段名的话,就不需要第二步了:
①:在yml配置文件中:
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: #全局的逻辑删除实体字段名,配置后可以不用@TableLogic注解: logic-delete-field: deleted logic-delete-value: 1 #逻辑删除 logic-not-delete-value: 0 #逻辑未删除②、
//@TableLogic //这个注解可以省略, //但是如果你这里属性不是deleted,而是别的,比如abc: //那么此时这个注解不能省略 private Integer deleted;
②、修改表结构,然后在这里面加一个字段:
代码举例一:查询
我们现在来做一个查询操作:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value="user")//去绑定它对应的数据库里的表名
public class UserInfo implements Serializable {
//创建实体类我们习惯性的去序列化一下
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
//它就会从数据库中查询email属性:
@TableField(value = "email",select = false)
private String mail;
private Integer age;
@TableField(exist = false)
private Integer status;
//@TableLogic
//这个注解可以省略,
//但是如果你这里属性不是deleted,而是别的,比如abc:
//那么此时这个注解不能省略
private Integer deleted;
}
@Test
public void testTableField2(){
List<UserInfo> userInfos = userInfoMapper.selectList(null);
userInfos.forEach(info -> System.out.println(info));
}
代码举例二:插入
@Test
public void testTableField3(){
UserInfo info = new UserInfo(null,"王五","@qq.com",23,1,null);
int result = userInfoMapper.insert(info);
System.out.println(result);
}
代码举例三:删除
@Test
public void testTableField5(){
UserInfo info = new UserInfo(null,"王五33","@qq.com",23,1,null);
int result = userInfoMapper.deleteById(15);
System.out.println(result);
}
看这个sql语句:我们做的是删除,但是他实际执行的update更新操作
举例四:
如果你不采用统一的字段名,你就可以不配logic-delete-field: deleted
但是这两个呢还是要配一下:
logic-delete-value: 1 # 逻辑已删除
logic-not-delete-value: 0 # 逻辑未删除
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value="user")//去绑定它对应的数据库里的表名
public class UserInfo implements Serializable {
//创建实体类我们习惯性的去序列化一下
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
//它就会从数据库中查询email属性:
@TableField(value = "email")
private String mail;
private Integer age;
@TableField(exist = false)
private Integer status;
//@TableLogic
//这个注解可以省略,
//但是如果你这里属性不是deleted,而是别的,比如abc:
//那么此时这个注解不能省略
@TableLogic
private Integer abc;
}
@Test
public void testTableField5(){
UserInfo info = new UserInfo(null,"王五33","@qq.com",23,1,null);
int result = userInfoMapper.deleteById(14);
System.out.println(result);
}
5.5、@Version
5.5.1.乐观锁的简介:
乐观锁@Version 注解,当要更新一条记录的时候,希望这条记录没有被别人更新。
乐观锁实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时,set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
通过乐观锁的话,我们并发的情况下来操作一些公共的数据、大家都需要修改的数据。那么我们持开放态度,所以大家的效率会很快,大家不会超时, 但是只有第一个人更新成功,后面的人的因为版本号不一致,所以导致他会更新失败。更新失败不是出错,没有出错,只是说他更新的成功数是0。
乐观锁可以在并发情况下提高程序的处理效率,不会出现死锁的情况。但是这种并发量如果特别大的话,它可能也会有问题,一般来说可能一两千以内的并发量用乐观锁来处理是一种比较好的方式。如果并发量很大,比如说我们的这种京东上的秒杀,那你用乐观锁这个性能上还是达不到要求。因为数据库它承载不了这么大的并发量,所以呢我们需要通过别的方式来处理。
5.5.2.使用步骤:
①、在需要改变的实体类中加version注解:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
//创建实体类我们习惯性的去序列化一下
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private String email;
private Integer age;
@TableField(exist = false)
private Integer status;
private Integer deleted;
@Version
private Integer version;
private Integer balance;
}
②、添加乐观锁相关的拦截器
@Configuration
public class MPConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//创建mybatisplus拦截器
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//向拦截器中添加乐观锁插件
//这里可以添加n个拦截器,等下我们还要添加分页拦截器:
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
5.2.3.举例一:加@Version
当加了@Version版本控制时:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
//创建实体类我们习惯性的去序列化一下
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private String email;
private Integer age;
@TableField(exist = false)
private Integer status;
private Integer deleted;
@Version
private Integer version;
private Integer balance;
}
@Test
public void testVersion(){
User wang = userMapper.selectById(1);
User li = userMapper.selectById(1);
//王将余额+50:
wang.setBalance(wang.getBalance() + 50);
// 王将数据存入数据库:
userMapper.updateById(wang);
//李将余额-20:
li.setBalance(li.getBalance()-20);
//李将数据存入数据库:
userMapper.updateById(li);
}
成功:
==> Preparing: SELECT id,name,email,age,deleted,version,balance FROM user WHERE id=? AND deleted=0
==> Parameters: 1(Integer)
<== Columns: id, name, email, age, deleted, version, balance
<== Row: 1, 张三, qq.com, 23, 0, 1, 100
<== Total: 1
==> Preparing: SELECT id,name,email,age,deleted,version,balance FROM user WHERE id=? AND deleted=0
==> Parameters: 1(Integer)
<== Columns: id, name, email, age, deleted, version, balance
<== Row: 1, 张三, qq.com, 23, 0, 1, 100
<== Total: 1
==> Preparing: UPDATE user SET name=?, email=?, age=?, version=?, balance=? WHERE id=? AND version=? AND deleted=0
==> Parameters: 张三(String), qq.com(String), 23(Integer), 2(Integer), 150(Integer), 1(Integer), 1(Integer)
<== Updates: 1
==> Preparing: UPDATE user SET name=?, email=?, age=?, version=?, balance=? WHERE id=? AND version=? AND deleted=0
==> Parameters: 张三(String), qq.com(String), 23(Integer), 2(Integer), 80(Integer), 1(Integer), 1(Integer)
<== Updates: 0
5.2.4.举例二:不加@Version:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
//创建实体类我们习惯性的去序列化一下
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private String email;
private Integer age;
@TableField(exist = false)
private Integer status;
private Integer deleted;
//@Version
private Integer version;
private Integer balance;
}
@Test
public void testVersion(){
User wang = userMapper.selectById(1);
User li = userMapper.selectById(1);
//王将余额+50:
wang.setBalance(wang.getBalance() + 50);
// 王将数据存入数据库:
userMapper.updateById(wang);
//李将余额-20:
li.setBalance(li.getBalance()-20);
//李将数据存入数据库:
userMapper.updateById(li);
}
余额变成了80:显然不对:
==> Preparing: UPDATE user SET name=?, email=?, age=?, version=?, balance=? WHERE id=? AND deleted=0
==> Parameters: 张三(String), qq.com(String), 23(Integer), 2(Integer), 150(Integer), 1(Integer)
<== Updates: 1
==> Preparing: UPDATE user SET name=?, email=?, age=?, version=?, balance=? WHERE id=? AND deleted=0
==> Parameters: 张三(String), qq.com(String), 23(Integer), 2(Integer), 80(Integer), 1(Integer)
<== Updates: 1
5.2.5.完整做法:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
//创建实体类我们习惯性的去序列化一下
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private String email;
private Integer age;
@TableField(exist = false)
private Integer status;
private Integer deleted;
@Version
private Integer version;
private Integer balance;
}
@Test
public void testVersion(){
User wang = userMapper.selectById(1);
User li = userMapper.selectById(1);
//王将余额+50:
wang.setBalance(wang.getBalance() + 50);
// 王将数据存入数据库:
userMapper.updateById(wang);
//李将余额-20:
li.setBalance(li.getBalance()-20);
//李将数据存入数据库:
int result = userMapper.updateById(li);
if(result == 0){
li = userMapper.selectById(1);
//李将余额-20:
li.setBalance(li.getBalance()-20);
//李将数据存入数据库:
userMapper.updateById(li);
}
}
==> Preparing: SELECT id,name,email,age,deleted,version,balance FROM user WHERE id=? AND deleted=0
==> Parameters: 1(Integer)
<== Columns: id, name, email, age, deleted, version, balance
<== Row: 1, 张三, qq.com, 23, 0, 1, 100
<== Total: 1
==> Preparing: SELECT id,name,email,age,deleted,version,balance FROM user WHERE id=? AND deleted=0
==> Parameters: 1(Integer)
<== Columns: id, name, email, age, deleted, version, balance
<== Row: 1, 张三, qq.com, 23, 0, 1, 100
<== Total: 1
==> Preparing: UPDATE user SET name=?, email=?, age=?, version=?, balance=? WHERE id=? AND version=? AND deleted=0
==> Parameters: 张三(String), qq.com(String), 23(Integer), 2(Integer), 150(Integer), 1(Integer), 1(Integer)
<== Updates: 1
==> Preparing: UPDATE user SET name=?, email=?, age=?, version=?, balance=? WHERE id=? AND version=? AND deleted=0
==> Parameters: 张三(String), qq.com(String), 23(Integer), 2(Integer), 80(Integer), 1(Integer), 1(Integer)
<== Updates: 0
==> Preparing: SELECT id,name,email,age,deleted,version,balance FROM user WHERE id=? AND deleted=0
==> Parameters: 1(Integer)
<== Columns: id, name, email, age, deleted, version, balance
<== Row: 1, 张三, qq.com, 23, 0, 2, 150
<== Total: 1
==> Preparing: UPDATE user SET name=?, email=?, age=?, version=?, balance=? WHERE id=? AND version=? AND deleted=0
==> Parameters: 张三(String), qq.com(String), 23(Integer), 3(Integer), 130(Integer), 1(Integer), 2(Integer)
<== Updates: 1