# Springboot 数据库连接
# Spring-Jpa-data连接数据库
# 配置
yml
spring:
datasource: # 配置数据源
url: jdbc:mysql://localhost:3306/test_db?useSSL=false&autoReconnect=true&characterEncoding=utf8
driver-class-name: com.mysql.jdbc.Driver # 驱动程序类名
username: root
password: 123456
jpa:
generate-ddl: false # 禁止jpa生成数据库模式
show-sql: false # 控制台不显示sql语句
properties:
hibernate:
dialect: org.hibernate.dialect.MySQLDialect
format_sql: true # 格式化SQL语句使之更易读
use-new-id-generator-mappings: false #不使用新的ID生成器映射
依赖
<!-- java连接器-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- jpa连接mysql-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.github.wenhao</groupId>
<artifactId>jpa-spec</artifactId>
<version>3.1.0</version>
</dependency>
# JPA方式特点
- JPA方式用泛型定义接口,以及方法
- spec是用于构建动态查询条件的对象
- pageable是分页对象
# 使用方法
- 定义数据仓库接口 继承BaseEntity
@NoRepositoryBean
public interface IBaseDao<T extends BaseEntity, I extends Serializable>
extends JpaRepository<T,I>, JpaSpecificationExecutor<T> {
}
- 实体对象接口,实体对象,实体对象的查询对象
public interface BaseEntity extends Serializable {
}
@Getter
@Setter
@ToString // 自动实现tostring方法
@Entity
@Table(name = "tb_user")
public class User implements BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
private String userName;
private String password;
// 级联关系,关联的实体立即会被加载,二层级联
@ManyToMany(cascade = {CascadeType.REFRESH}, fetch = FetchType.EAGER)
@JoinTable(name = "tb_user_role", joinColumns = {
@JoinColumn(name = "user_id")},inverseJoinColumns = {@JoinColumn(name = "role_id")})
private Set<Role> roles;
}
@Getter
@Setter
@ToString
@Entity
@Table(name = "tb_role")
public class Role implements BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id",nullable = false)
private Long id;
private String name;
private String roleKey;
private String description;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
- 服务接口,服务实现类, 需要使用数据访问对象
@Service
public class RoleDoServiceImpl extends BaseDoServiceImpl<Role, Long> implements IRoleService {
private final IRoleDao roleDao;
public RoleDoServiceImpl(final IRoleDao roleDao2) {
this.roleDao = roleDao2;
}
@Override
public IBaseDao<Role, Long> getBaseDao() {
return this.roleDao;
}
@Override
public Page<Role> findPage(RoleQueryBean roleQueryBean, PageRequest pageRequest) {
Specification<Role> specification = Specifications.<Role>and()
.like(StringUtils.isNotEmpty(roleQueryBean.getName()), "name",roleQueryBean.getName())
.like(StringUtils.isNotEmpty(roleQueryBean.getDescription()), "description", roleQueryBean.getDescription())
.build();
return this.roleDao.findAll(specification, pageRequest);
}
}
- 封装一个返回结果的泛型类(这个不是JPA特点)
public static <T> ResponseResult<T> fail(T data, String message) {
return ResponseResult.<T>builder()
.data(data)
.message(message)
.status(ResponseStatus.FAIL.getResponseCode())
.timestamp(System.currentTimeMillis())
.build();
}
# Springboot-Mybatis
# xml方式使用
要点
- 配置yml mybatis连接选项 datasource连接选项
- 配置dao @Mapper注解
- 配置实体对象,查询对象,返回的结果对象 类似于jpa
- 配置service 接口+实现 类似与jpa
- controller中使用 类似与jpa
- mapper.xml 写SQL配置数据库查询语句和方法,表关联在resultmap中配置
# 注解方式使用
- 通过注解去实现mapper.xml里的功能,实际上知识一种框架的两种写法而已,本质并没有什么不同
- ResultMap 可以定义在随便一个方法上,支持ID引用进行复用
- 表关联通过@Results+@Many注解实现 many连接到关联表的查询方法上 一对一可以使用@One注解
- id是否使用数据库自增,可以使用selectkey
- @Mapper注解告诉mybatis框架该接口是个mapper接口需要被框架应用扫描并生成实现类
- 启用之后可以在方法上使用@Select @Delect等注解
思考:
- 直接在方法上写注解是很不方便的,可以使用provider
public class UserDaoProvider {
public String findById(final Long id) {
SQL sql = new SQL();
sql.SELECT("u.id, u.password, u.user_name, u.email, u.phone_number, u.description, u.create_time, u.update_time");
sql.FROM("tb_user u");
sql.WHERE("id = " + id);
return sql.toString();
}
}
- 通过@SelectProvider方法关联到定义的类和方法
@ResultMap("UserResult")
@SelectProvider(type = UserDaoProvider.class, method = "findById")
User findById2(Long id);
# 两者结合使用
- 将复杂的SQL写在xml中,例如resultMap
- 然后在方法上使用@ResultMap注解
原文链接 https://www.pdai.tech/md/spring/springboot/springboot-x-mysql-mybatis-anno.html
# 常见的坑
mybatis的xml映射文件中,注释一定要使用
不能用--,用后者的话#{params} 会出错
# PageHelper插件
# 引入插件
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>最新版本</version>
</dependency>
# 配置拦截器插件
MyBatis的xml文件中配置
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
<property name="param1" value="value1"/>
</plugin>
</plugins>
官方文档 https://pagehelper.github.io/docs/howtouse/
参数说明
- helperDialect 指定数据库方言
- offsetAsPageNum true时将offset作为pagenum使用
# 使用
PageHelper.startPage(1, 10);
List<Country> list = countryMapper.selectIf(1);
# Mybatis Plus
Mybatis Plus是对Mybatis的增强,启动后会自动注入基本CRUD,少量配置可实现单表大部分CRUD操作。
- 内置分页插件
- 内置代码生成器
- 内置性能分析插件
可以通过配置模板引擎,生成entity, Mapper Interface, Mapper xml, service ,controller代码
# 引入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
# yml配置
mybatis-plus:
configuration:
cache-enabled: true
use-generated-keys: true
default-executor-type: REUSE
use-actual-param-name: true
# 使用(基于注解)
- Mapper层 继承 BaseMapper
public interface IUserDao extends BaseMapper<User> {
List<User> findList(UserQueryBean userQueryBean);
}
- Service层继承 ServiceImpl 并实现对应接口
public class RoleDoServiceImpl extends ServiceImpl<IRoleDao, Role> implements IRoleService {}
- lambda函数式查询
@Override
public List<Role> findList(RoleQueryBean roleQueryBean) {
return lambdaQuery().like(StringUtils.isNotEmpty(roleQueryBean.getName()), Role::getName, roleQueryBean.getName())
.like(StringUtils.isNotEmpty(roleQueryBean.getDescription()), Role::getDescription, roleQueryBean.getDescription())
.like(StringUtils.isNotEmpty(roleQueryBean.getRoleKey()), Role::getRoleKey, roleQueryBean.getRoleKey())
.list();
}
- 分页采用内置的MybatisPlusIntercetor
@Bean
public PaginationInnerInterceptor paginationInnerInterceptor() {
return new PaginationInnerInterceptor();
}
- 复杂的关联查询,配置xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="tech.pdai.springboot.mysql8.mybatisplus.anno.dao.IUserDao">
<resultMap type="tech.pdai.springboot.mysql8.mybatisplus.anno.entity.User" id="UserResult">
<id property="id" column="id" />
<result property="userName" column="user_name" />
<result property="password" column="password" />
<result property="email" column="email" />
<result property="phoneNumber" column="phone_number" />
<result property="description" column="description" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<collection property="roles" ofType="tech.pdai.springboot.mysql8.mybatisplus.anno.entity.Role">
<result property="id" column="id" />
<result property="name" column="name" />
<result property="roleKey" column="role_key" />
<result property="description" column="description" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
</collection>
</resultMap>
<sql id="selectUserSql">
select u.id, u.password, u.user_name, u.email, u.phone_number, u.description, u.create_time, u.update_time, r.name, r.role_key, r.description, r.create_time, r.update_time
from tb_user u
left join tb_user_role ur on u.id=ur.user_id
inner join tb_role r on ur.role_id=r.id
</sql>
<select id="findList" parameterType="tech.pdai.springboot.mysql8.mybatisplus.anno.entity.query.UserQueryBean" resultMap="UserResult">
<include refid="selectUserSql"/>
where u.id != 0
<if test="userName != null and userName != ''">
AND u.user_name like concat('%', #{user_name}, '%')
</if>
<if test="description != null and description != ''">
AND u.description like concat('%', #{description}, '%')
</if>
<if test="phoneNumber != null and phoneNumber != ''">
AND u.phone_number like concat('%', #{phoneNumber}, '%')
</if>
<if test="email != null and email != ''">
AND u.email like concat('%', #{email}, '%')
</if>
</select>
</mapper>
# 字段隔离多租户
多用户公用相同的系统或程序组件,并且仍可确保用户数据的隔离性
在数据存储上的实现方式:
- DB隔离:独立数据库
- Schema隔离:共享数据库,隔离数据架构
- 字段隔离,共享数据库,通过TenantID区分租户数量
使用TenantLineInnerInterceptor插件
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
// 获取租户ID,实际应用可以从线程本地变量,请求参数,或者其他途径获取
@Override
public Expression getTenantId() {
return new LongValue(1);
}
// 租户列名
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
// 设置要忽略的表
@Override
public boolean ignoreTable(String tableName) {
return false;
}
// 插入数据是是否忽略租户拦截
@Override
public boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {
return TenantLineHandler.super.ignoreInsert(columns, tenantIdColumn);
}
}));
租户和用户的区别,租户之间数据是隔离的,用户不是
# 其他插件
- 自动分页: PaginationInnerInterceptor
- 多租户: TenantLineInnerInterceptor
- 动态表名: DynamicTableNameInnerInterceptor
- 乐观锁: OptimisticLockerInnerInterceptor
- sql 性能规范: IllegalSQLInnerInterceptor
- 防止全表更新与删除: BlockAttackInnerInterceptor