<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybaits-plus第三方提供的启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.1</version>
</dependency>
导入上面这个增强版依赖之后,下面两个依赖无需再次导入:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
spring:
datasource: #是否使用安全连接
#mysal 8驱动不同com.mysql.cj.jdbc.Driver,还需要增加时区的配置 serverTimezone=GMT%2B8
url: jdbc:mysql://localhost:3306/tx?userSSL=false&useUnicode=true&characterEncoding=utf-8
username: root
password: 126433
driver-class-name: com.mysql.jdbc.Driver
//继承BaseMapper基本类,接口里面已经写了很多的crud方法
@Mapper
public interface UserMapper extends BaseMapper<User>
{}
@SpringBootTest
class SpringBootDaoApplicationTests
{
//继承了BaseMapper,所有的方法都来自于父类,我们页可以编写自己的扩展方法
@Autowired
UserMapper userMapper;
@Test
void contextLoads()
{
//参数是一个Wrapper,条件构造器,这里我们先不使用 null
//查询全部用户
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
1、@Mapper注解:
作用:在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类
添加位置:接口类上面
@Mapper
public interface UserDAO {
//代码
}
如果想要每个接口都要变成实现类,那么需要在每个接口类上加上@Mapper注解,比较麻烦,解决这个问题用@MapperScan
2、@MapperScan
作用:指定要变成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类
添加位置:是在Springboot启动类上面添加,
@SpringBootApplication
@MapperScan("com.winter.dao")
public class SpringbootMybatisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisDemoApplication.class, args);
}
}
添加@MapperScan(“com.winter.dao”)注解以后,com.winter.dao包下面的接口类,在编译之后都会生成相应的实现类
#开启日志功能
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #控制台输出
这个注解表示表的主键
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User
{
String name;
Integer age;
@TableId(value="id",type = IdType.AUTO)
Integer id;
}
接受两个参数
value = 主键列名
type = 主键类型
可选类型:
使用@TableId注解时,类型为: type = IdType.AUTO 数据库也需要设置自增 否则会出 Cause: java.sql.SQLException: Field ‘id’ doesn’t have a default value
//测试插入
@Test
public void testInsert(){
User user = new User();
user.setName("淮城一只猫");
user.setAge(5);
user.setEmail("2424496907@qq.com");
int result = userMapper.insert(user); //自动生成id
System.out.println(result); //受影响的行数
System.out.println(user); //发现id自动回填
}
默认使用ID_WORKER,全局唯一id----雪花算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!
我们需要配置注解自增:
public enum IdType {
AUTO(0), //id自增
NONE(1), //未设置主键
INPUT(2), //手动输入,一旦手动输入后,就需要自己去写id,否则id为空
ID_WORKER(3), //默认值,全局唯一id
UUID(4), //全局唯一id,uuid
ID_WORKER_STR(5); //ID_WORKER的字符串表示法
}
//测试更新
@Test
public void testUpdate(){
User user = new User();
user.setId(6L);
user.setName("我的博客叫:淮城一只猫");
user.setAge(6);
user.setEmail("2424496907@qq.com");
//注意:updateById参数是一个对象
int result = userMapper.updateById(user); //自动生成id
System.out.println(result); //受影响的行数
}
创建时间、修改时间!这些操作一般自动化完成的,我们不希望手动更新!
阿里巴巴开发手册:所有的数据库表:gmt_create、gmt_modified几乎所有表都要配置上!而且需要自动化!
private Date creteTime;
private Date updateTime;
mysql中的日期类型小结笔记 MySQL-datetime类型的列设置默认值为CURRENT_TIMESTAMP MySQL中datetime字段的默认值CURRENT_TIMESTAMP
//字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段
// 注意!这里需要标记为填充字段
@TableField(fill = FieldFill.INSERT)
private Date creteTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
策略枚举如下:
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入填充字段
*/
INSERT,
/**
* 更新填充字段
*/
UPDATE,
/**
* 插入和更新填充字段
*/
INSERT_UPDATE
}
@Slf4j
@Component//填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component或@Bean注入
public class MyMetaObjectHandler implements MetaObjectHandler
{
//插入时填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//更新时填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
@SpringBootTest
class SpringBootDaoApplicationTests
{
//继承了BaseMapper,所有的方法都来自于父类,我们页可以编写自己的扩展方法
@Autowired
UserMapper userMapper;
@Test
void contextLoads()
{
User user=new User();
user.setAge(18);
user.setName("hhhh");
int ret = userMapper.insert(user);
System.out.println(user);
}
}
乐观锁:顾名思义乐观,它总是认为不会出现问题,无论干什么都不去上锁!如果出现问题,再次更新值测试
悲观锁:顾名思义悲观,它总是认为会出现问题,无论干什么都会加上锁!再去操作
乐观锁实现方式:
@Version //乐观锁注解
private Integer version;
说明:
@MapperScan("com.Mapper")
@EnableTransactionManagement //自动管理事务(默认也是开启的)
@Configuration //配置类
public class MybaitsPlusConfig {
//注册乐观锁插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
}
@SpringBootTest
class SpringBootDaoApplicationTests
{
//继承了BaseMapper,所有的方法都来自于父类,我们页可以编写自己的扩展方法
@Autowired
UserMapper userMapper;
//测试乐观锁成功!
@Test
public void testOptimisticLocker1() {
//1.查询用户信息
User user = userMapper.selectById(276);
//2.修改用户信息
user.setName("aaa");
user.setAge(18);
//3.执行更新操作
userMapper.updateById(user);
}
//测试乐观锁失败!多线程下
@Test
public void testOptimisticLocker2() {
//线程1
User user1 = userMapper.selectById(276);
user1.setName("aaa");
//模拟另外一个线程执行插队操作
User user2 = userMapper.selectById(276);
user2.setName("bbb");
userMapper.updateById(user2);
//自旋锁多次操作尝试提交
userMapper.updateById(user1);
}
}
@SpringBootTest
class SpringBootDaoApplicationTests
{
//继承了BaseMapper,所有的方法都来自于父类,我们页可以编写自己的扩展方法
@Autowired
UserMapper userMapper;
//测试查询
@Test
public void testSelectById() {
User user = userMapper.selectById(276);
System.out.println("===========================");
System.out.println(user);
}
//测试批量查询
@Test
public void testSelectByBatchId()
{
List<User> users = userMapper.selectBatchIds(Arrays.asList(270, 271, 272));
System.out.println("===========================");
users.forEach(System.out::println);
}
//条件查询之一 使用map操作
@Test
public void testSelectBatchIds() {
HashMap<String, Object> map = new HashMap<>();
//自定义查询,同时满足map集合中的所有条件
map.put("name","bbb");
map.put("age","18");
List<User> users = userMapper.selectByMap(map);
System.out.println("===========================");
users.forEach(System.out::println);
}
}
分页在网站中使用非常多!
//Spring boot方式
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {
// 旧版
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
// 最新版
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
}
//测试分页查询
@Test
public void testPage() {
//参数一:当前页
//参数二:页面大小
//使用了分页插件之后,所有的分页操作页变得简单了
Page<User> page = new Page<>(1,5);
//第二个参数是查询条件
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println);
System.out.println(page.getTotal());
}
分页查询常用的方法:
@Test
public void testPage() {
Page<Employee> page = new Page<>(1,1);
List<Employee > emps =
employeeMapper.selectPage(page, null);
System.out.println(emps);
System.out.println("===============获取分页相关的一些信息======================");
System.out.println("总条数:" +page.getTotal());
System.out.println("当前页码: "+ page.getCurrent());
System.out.println("总页码:" + page.getPages());
System.out.println("每页显示的条数:" + page.getSize());
System.out.println("是否有上一页: " + page.hasPrevious());
System.out.println("是否有下一页: " + page.hasNext());
//将查询的结果封装到page对象中
page.setRecords(emps);
}
//通过id删除
@Test
public void testDeleteById() {
userMapper.deleteById(174);
}
//通过id批量删除
@Test
public void testDeleteBatchId() {
userMapper.deleteBatchIds(Arrays.asList(177, 178));
}
//通过map删除
@Test
public void testDeleteMap() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "大忽悠1号");
userMapper.deleteByMap(map);
}
物理删除:从数据库中直接移除
逻辑删除:在数据库中没有被移除,而是通过一个变量来让他失效!delete=0 => delete=1
管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!
只对自动注入的sql起效:
插入: 不作限制 查找: 追加where条件过滤掉已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段 更新: 追加where条件防止更新到已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段 删除: 转变为 更新
例如:
删除: update user set deleted=1 where id = 1 and deleted=0 (如果走了自动注入sql的删除操作,那么其实执行的是更新操作,即将deleted值变为1,即逻辑删除掉了) 查找: select id,name,deleted from user where deleted=0 (查找的时候只会查找出没有被逻辑删除掉的用户)
字段类型支持说明:
支持所有数据类型(推荐使用 Integer,Boolean,LocalDateTime) 如果数据库字段使用datetime,逻辑未删除值和已删除值支持配置为字符串null,另一个值支持配置为函数来获取值如now()
附录:
逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示。
测试一下:
//逻辑删除字段
@TableLogic
private Integer deleted;
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
@SpringBootTest
class SpringBootDaoApplicationTests
{
//继承了BaseMapper,所有的方法都来自于父类,我们页可以编写自己的扩展方法
@Autowired
UserMapper userMapper;
//通过id删除
@Test
public void testDeleteById()
{
userMapper.deleteById(174);
}
}
我们在开发中,会遇到一些慢sql,我们有必要把它揪出来 。测试!druid…
MP也提供性能分析插件,如果超过这个时间就停止运行!官方3.1.0以上版本推荐使用p6spy!
P6Spy 是一个可以用来在应用程序中拦截和修改数据操作语句的开源框架。 通过 P6Spy 我们可以对 SQL 语句进行拦截,相当于一个 SQL 语句的记录器,这样我们可以用它来作相关的分析,比如性能分析。
注意:该插件有性能损耗,建议平时开发测试时使用,不建议生产环境使用。
<!--p6spy性能分析插件-->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.0</version>
</dependency>
P6Spy 工作原理:P6Spy 通过劫持 JDBC 驱动,在调用实际 JDBC 驱动前拦截调用的目标语,达到 SQL 语句日志记录的目的。
spring:
datasource: #是否使用安全连接
#mysal 8驱动不同com.mysql.cj.jdbc.Driver,还需要增加时区的配置 serverTimezone=GMT%2B8
url: jdbc:p6spy:mysql://localhost:3306/tx?userSSL=false&useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
profiles:
active: dev #激活开发环境
#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式gui
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
@SpringBootTest
class SpringBootDaoApplicationTests
{
//继承了BaseMapper,所有的方法都来自于父类,我们页可以编写自己的扩展方法
@Autowired
UserMapper userMapper;
//通过id删除
@Test
@Profile({"dev","test"})
public void testDeleteById()
{
User user = userMapper.getUserId(174);
System.out.println(user);
}
}
1. 上面的样例配置,我们是直接将日志输出到控制台,我们可以修改 appender 配置属性,使用日志系统记录 sql:
2. 再次执行查询,可以看到现在改用日志系统记录 sql 了:
也可以修改 appender 配置属性,将日志输出到文件中:
#日志输出到文件
appender=com.p6spy.engine.spy.appender.FileLogger
# 指定 Log 的文件名 默认 spy.log
logfile=spy.log
# 指定是否每次是增加 Log,设置为 false 则每次都会先进行清空 默认true
append=true
1. 如果觉得默认的日志格式不合适,我们也可以使用 logMessageFormat 和 customLogMessageFormat 这两个配置参数来自定义日志输出格式:
可用的变量为: %(connectionId):connection id %(currentTime):当前时间 %(executionTime):执行耗时 %(category):执行分组 %(effectiveSql):提交的 SQL 换行 %(effectiveSqlSingleLine):提交的 SQL 不换行显示 %(sql):执行的真实 SQL 语句,已替换占位 %(sqlSingleLine):执行的真实 SQL 语句,已替换占位不换行显示
6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat=%(currentTime) |
SQL耗时: %(executionTime) ms |
连接信息: %(category)-%(connectionId) | 执行语句: %(sql)
@Test
void test1() {
//查询name不为空的用户,并且邮箱不为空的,年龄大于等于12
QueryWrapper<User> wapper = new QueryWrapper<>();
wapper.isNotNull("name") //这里推荐使用链式编程
.ge("age", 64);//大于等于
userMapper.selectList(wapper).forEach(System.out::println);
}
@Test
void test2() {
//查询名字为Tom
QueryWrapper<User> wapper = new QueryWrapper<>();
wapper.eq("name","bbb");
//只查询一个用户
User user = userMapper.selectOne(wapper);
System.out.println(user);
}
//范围查询
@Test
void test3()
{
//查询年龄在20~30岁之间的用户
QueryWrapper<User> wapper = new QueryWrapper<>();
wapper.between("age", 64, 68);//区间
System.out.println(userMapper.selectCount(wapper));//查询结果数
}
//模糊查询
@Test
void test4() {
//查询名字有
QueryWrapper<User> wapper = new QueryWrapper<>();
wapper.notLike("name","哈")//%e%
.likeRight("name","b");//t%
List<Map<String, Object>> maps = userMapper.selectMaps(wapper);
maps.forEach(System.out::println);
}
//子查询
@Test
void test5() {
QueryWrapper<User> wapper = new QueryWrapper<>();
//id在子查询中查出来
wapper.inSql("id","select id from user where id>273");
List<Object> objects = userMapper.selectObjs(wapper);
objects.forEach(System.out::println);
}
//排序
@Test
void test6() {
QueryWrapper<User> wapper = new QueryWrapper<>();
//通过id进行排序
wapper.orderByDesc("id");
userMapper.selectList(wapper).forEach(System.out::println);
}
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
EasyCode是基于IntelliJ IDEA Ultimate版开发的一个代码生成插件,主要通过自定义模板(基于velocity)来生成各种你想要的代码。通常用于生成Entity、Dao、Service、Controller。如果你动手能力强还可以用于生成HTML、JS、PHP等代码。理论上来说只要是与数据有关的代码都是可以生成的。
基于Mybatis底层强大的逆向工程能力和良好的项目架构
IntelliJ IDEA Ultimate版
因为是基于Database Tool开发,所有Database Tool支持的数据库都是支持的。
包括如下数据库:
MySQL SQL Server Oracle PostgreSQL Sqlite Sybase Derby DB2 HSQLDB H2 当然支持的数据库类型也会随着Database Tool插件的更新同步更新。
支持多表同时操作 支持同时生成多个模板 支持自定义模板 支持自定义类型映射(支持正则) 支持自定义附加列 支持列附加属性 所有配置项目支持分组模式,在不同项目(或选择不同数据库时),只需要切换对应的分组,所有配置统一变化
使用Easy Code一定要使用IDEA自带的数据库工具来配置数据源
打开侧边的Database,查看效果
提前准备的数据表
第一次安装EasyCode的时候默认的模板(服务于MyBatis)可以生成下面类型的代码
entity.java dao.java service.java serviceImpl.java controller.java mapper.xml debug.json
选择模板
点击OK之后,就可以看到生成了这些代码
实体类层:entity.java
##引入宏定义
$!define
##使用宏定义设置回调(保存位置与文件后缀)
#save("/entity", ".java")
##使用宏定义设置包后缀
#setPackageSuffix("entity")
##使用全局变量实现默认包导入
$!autoImport
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
##使用宏定义实现类注释信息
#tableComment("实体类")
@AllArgsConstructor
@Data
@Builder
public class $!{tableInfo.name} implements Serializable {
private static final long serialVersionUID = $!tool.serial();
#foreach($column in $tableInfo.fullColumn)
#if(${column.comment})/**
* ${column.comment}
*/#end
private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
#end
}
选择好分组后,点击OK,之后在Datebase视图的数据表右键选择EasyCode生成的时候会让你选择当前分组的模板