# 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方式特点

  1. JPA方式用泛型定义接口,以及方法
  2. spec是用于构建动态查询条件的对象
  3. pageable是分页对象

# 使用方法

  1. 定义数据仓库接口 继承BaseEntity
@NoRepositoryBean
public interface IBaseDao<T extends BaseEntity, I extends Serializable>
    extends JpaRepository<T,I>, JpaSpecificationExecutor<T> {
}
  1. 实体对象接口,实体对象,实体对象的查询对象
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;
}
  1. 服务接口,服务实现类, 需要使用数据访问对象
@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);
    }

}
  1. 封装一个返回结果的泛型类(这个不是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方式使用

要点

  1. 配置yml mybatis连接选项 datasource连接选项
  2. 配置dao @Mapper注解
  3. 配置实体对象,查询对象,返回的结果对象 类似于jpa
  4. 配置service 接口+实现 类似与jpa
  5. controller中使用 类似与jpa
  6. mapper.xml 写SQL配置数据库查询语句和方法,表关联在resultmap中配置

# 注解方式使用

  1. 通过注解去实现mapper.xml里的功能,实际上知识一种框架的两种写法而已,本质并没有什么不同
  2. ResultMap 可以定义在随便一个方法上,支持ID引用进行复用
  3. 表关联通过@Results+@Many注解实现 many连接到关联表的查询方法上 一对一可以使用@One注解
  4. id是否使用数据库自增,可以使用selectkey
  5. @Mapper注解告诉mybatis框架该接口是个mapper接口需要被框架应用扫描并生成实现类
  • 启用之后可以在方法上使用@Select @Delect等注解

思考:

  1. 直接在方法上写注解是很不方便的,可以使用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();
    }
}
  1. 通过@SelectProvider方法关联到定义的类和方法
@ResultMap("UserResult")
@SelectProvider(type = UserDaoProvider.class, method = "findById")
User findById2(Long id);

# 两者结合使用

  1. 将复杂的SQL写在xml中,例如resultMap
  2. 然后在方法上使用@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

# 使用(基于注解)

  1. Mapper层 继承 BaseMapper
public interface IUserDao extends BaseMapper<User> {
    List<User> findList(UserQueryBean userQueryBean);
}
  1. Service层继承 ServiceImpl 并实现对应接口
public class RoleDoServiceImpl extends ServiceImpl<IRoleDao, Role> implements IRoleService {}
  1. 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();
}
  1. 分页采用内置的MybatisPlusIntercetor
@Bean
public PaginationInnerInterceptor paginationInnerInterceptor() {
    return new PaginationInnerInterceptor();
}
  1. 复杂的关联查询,配置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
Last Updated: 12/23/2024, 4:18:13 AM