# MyBatis

MyBatis 是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。将接口和 Java 的 POJOs(Plain Ordinary Java Objects,普通的 Java对象)映射成数据库中的表。

MyBatis是对JDBC的封装。相对于JDBC,MyBatis有以下优点:

  • 优化获取和释放
    我们一般在访问数据库时都是通过数据库连接池来操作数据库,数据库连接池有好几种,比如C3P0、DBCP,也可能采用容器本身的JNDI数据库连接池。我们可以通过DataSource进行隔离解耦,我们统一从DataSource里面获取数据库连接,DataSource具体由DBCP实现还是由容器的JNDI实现都可以,所以我们将DataSource的具体实现通过让用户配置来应对变化。

  • SQL统一管理,对数据库进行存取操作
    我们使用JDBC对数据库进行操作时,SQL查询语句分布在各个Java类中,这样可读性差,不利于维护,当我们修改Java类中的SQL语句时要重新进行编译。 Mybatis可以把SQL语句放在配置文件中统一进行管理,以后修改配置文件,也不需要重新就行编译部署。

  • 生成动态SQL语句
    我们在查询中可能需要根据一些属性进行组合查询,比如我们进行商品查询,我们可以根据商品名称进行查询,也可以根据发货地进行查询,或者两者组合查询。如果使用JDBC进行查询,这样就需要写多条SQL语句。

  • 能够对结果集进行映射
    我们在使用JDBC进行查询时,返回一个结果集ResultSet,我们要从结果集中取出结果封装为需要的类型 在Mybatis中我们可以设置将结果直接映射为自己需要的类型,比如:JavaBean对象、一个Map、一个List等等。

# 基本使用

  • 引入mybatis、mysql依赖
<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>3.4.5</version>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>8.0.32</version>
</dependency>
  • 编写映射文件

在resources目录下创建文件com/sylone/mapper/UserMapper.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="userMapper">
    <select id="findAll" resultType="com.sylone.pojo.User">
        select * from user
    </select>
	
	<!--根据ID查询-->
	<select id="findById" parameterType="java.lang.Integer" 
						 resultType="com.sylone.pojo.UserInfo">
		select * from user_info where id=#{id}
	</select>
	
	<!--模糊查询-->
	<select id="findByName" parameterType="string" resultType="com.itmentu.pojo.UserInfo">
	    select * from user_info where username like CONCAT('%',#{name},'%')
	</select>
	
	<!--插入操作-->
	<insert id="add" parameterType="com.sylone.pojo.User">
		insert into user(id,username,password) values(#{id},#{username},#{password})
	</insert>
	
	<!--获取自增ID-->
	<insert id="addId" useGeneratedKeys="true" keyProperty="id" parameterType="User">
	    insert  into user(username,password) values(#{username},#{password})
	</insert>
	
	<!--删除操作-->
	<delete id="delete" parameterType="java.lang.Integer">
		delete from user where id=#{id}
	</delete>
	
	<!--更新操作-->
	<update id="update" parameterType="com.wang.pojo.User">
		update user set username=#{username},password=#{password} where id=#{id}
	</update>
</mapper>
  • 配置mybatis核心配置文件

在resources目录下创建文件mybatis-config.xml(名称自定义)。并添加相应配置

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///test"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- 映射文件所在位置-->
        <mapper resource="com/sylone/mapper/UserMapper.xml"/>
    </mappers>
</configuration>
  • 测试
public static void main(String[] args) throws IOException {
	//加载核心配置文件
	InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
	//获得sqlSession工厂对象
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	//获得sqlSession对象,openSession(true),自动提交事务
	SqlSession session = sqlSessionFactory.openSession();
	
	//查询list
	List<User> userList = session.selectList("userMapper.findAll");
	Iterator<User> iterator = userList.iterator();
	while(iterator.hasNext()){
		System.out.println(iterator.next());
	}
	
	//查询单个
	UserInfo userInfo = session.selectOne("userMapper.findById"1);
    System.out.println(userInfo);
	
	//模糊查询
	UserInfo userInfo = session.selectOne("userMapper.findByName"'张三');
	System.out.println(userInfo);
	
	//插入操作
	int insert = session.insert("userMapper.add",new User("Curry","30303030"));
	System.out.println(insert);
	
	//获取自增ID
	UserInfo user = new UserInfo(3, "zhaoliu", "32323");
	int addId = sqlSession.insert("userMapper.addId", user);
	Integer id = user.getId();
	System.out.println(id);
	
	//删除操作
	int delete = session.delete("userMapper.delete",1);
	System.out.println(delete);
	
	//更新操作
	int update = session.update("userMapper.update",new User(2,"科比","24-8")); 
	System.out.println(update);
	
	//涉及数据库数据变化,所以要使用sqlSession对象显示的提交事务,即sqlSession.commit()
	session.commit();
    //关闭sqlSession对象
	session.close();
}

# 配置文件

# environments标签

主要是用来配置数据源环境,可以配置多个环境 environments

事务管理类型有两种:

  • JDBC:使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域
  • MANAGED:这个配置几乎没做什么。不常用

数据源类型有三种:

  • POOLED:这种数据源的实现利用“池”的概念将JDBC 连接对象组织起来
  • UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接
  • JNDI:容器自带的数据源

# mappers标签

用于加载映射文件,加载方式有如下几种

<!--使用相对于类路径资源(resources)的引用-->
<mapper resource="com/wang/mapper/UserMapper.xml"/>
<!--使用完全限定资源定位符(URL,磁盘上的位置)-->
<mapper url="file:///mapper/UserMapper.xml"/>
<!--使用映射器接口实现类的完全限定类名-->
<mapper class="com.wang.mapper.UserMapper"/>
<!--将包内的映射器接口实现全部注册为映射器-->
<package name="com.wang.mapper"/>

# properties标签

用于加载额外配置的properties文件,例如将数据源的配置信息单独抽取成一个properties文件

<!--在classpath下面创建一个db.properties数据库连接配置文件-->
mysql.driverClassName=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/gj1?characterEncoding=utf8
mysql.username=root
mysql.password=123456

<!--配置文件-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <!-- properties标签 :读取properties配置文件到mybatis框架中 -->
  <properties resource="db.properties"/>
  
  <environments default="dev_mysql">
    <environment id="dev_mysql">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
      	<!-- ${mysql.driverClassName} 属于OGNL语法,此处为${配置文件的key} -->
        <property name="driver" value="${mysql.driverClassName}"/>
        <property name="url" value="${mysql.url}"/>
        <property name="username" value="${mysql.username}"/>
        <property name="password" value="${mysql.password}"/>
      </dataSource>
    </environment>
  </environments>
  <!-- 配置映射文件 -->
  <mappers>
  	<mapper resource="com\mybatis\mapper\UserMapper.xml"/>
  </mappers>
</configuration>

MyBatis 在 3.4.2 版本之后加了一个默认值配置。首先,需要在 properties 标签中开启默认值配置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <properties resource="db.properties">
     <!--开启默认值配置-->
     <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" 
	           value="true"/>
  </properties>
  
  <environments default="dev_mysql">
    <environment id="dev_mysql">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${mysql.driverClassName}"/>
        <property name="url" value="${mysql.url}"/>
		<!--设置默认值配置-->
        <property name="username" value="${mysql.username:root}"/>
        <property name="password" value="${mysql.password:root}"/>
      </dataSource>
    </environment>
  </environments>
  <!-- 配置映射文件 -->
  <mappers>
  	<mapper resource="com\mybatis\mapper\UserMapper.xml"/>
  </mappers>
</configuration>

# typeAliases标签

MyBatis除了内置了常用类型的别名,自定义对象可以设置别名,每次直接用别名代替要转换的对象即可

<!--必须定义在setting或properties后面-->
<typeAliases>
    <typeAlias type="com.itmentu.pojo.UserInfo" alias="UserInfo" />
	<!--给com.itmentu.pojo包下的类都取个别名,类名默认是别名,也可通过@Alias("userInfo")指定-->
	<package name="com.itmentu.pojo"/>
</typeAliases>

# typeHandlers标签

数据库类型和Java类型之间的转换器。MyBatis已经默认提供了一些常见的类型处理器,你可以重写类型处理器或创建你自己的类型处理器

开发步骤:

  • 实现 org.apache.ibatis.type.TypeHandler接口,或继承一个类 org.apache.ibatis.type.BaseTypeHandler
  • 覆盖4个未实现的方法
public class DateTypeHandler extends BaseTypeHandler<Date> {
    // 将 java 类型转换成数据库需要的类型
    public void setNonNullParameter(PreparedStatement preparedStatement, int i
	                               , Date date, JdbcType jdbcType) throws SQLException {
        long time = date.getTime();
        preparedStatement.setLong(i, time);
    }
    // 将数据库中的类型 转换成java类型
    // String 参数 要转换的字段的名称
    // ResultSet 查询出的结果集
    public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
        // 获得结果集中需要的数据(long) 转换成Date类型 返回
        long aLong = resultSet.getLong(s);
        Date date = new Date(aLong);
        return date;
    }
    // 将数据库中的类型 转换成java类型
    public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
        long aLong = resultSet.getLong(i);
        Date date = new Date(aLong);
        return date;
    }
    // 将数据库中的类型 转换成java类型
    public Date getNullableResult(CallableStatement callableStatement
	                            , int i) throws SQLException {
        long aLong = callableStatement.getLong(i);
        Date date = new Date(aLong);
        return date;
    }
}
  • 在MyBatis核心配置文件中进行注册
<!--注册类型处理器-->
<typeHandlers>
    <typeHandler handler="com.lzjtu.handler.DateTypeHandler" />
</typeHandlers>
  • 测试转换是否正确
@Test
public void test2() throws IOException {

     InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
     SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
	                                                         .build(resourceAsStream);
     SqlSession sqlSession = sqlSessionFactory.openSession();

     UserMapper mapper = sqlSession.getMapper(UserMapper.class);

     User user = mapper.findById(0);
     System.out.println("user中的birthday: " + user.getBirthday());
     sqlSession.close();
 }
 //数据库中以long型存储,Java数据展示中以时间格式展示

# plugins标签

MyBatis可以使用第三方的插件来对功能进行扩展

分页助手 PageHelper 是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据

开发步骤:

  • 导入通用 PageHelper 的坐标
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>
  • 在 mybatis 核心配置文件中配置 PageHelper 插件
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
 
<configuration>
  <plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
      <!-- 该参数指明要连接的是哪个数据库 -->
      <property name="helperDialect" value="mysql"/>
      <!-- 支持通过Mapper接口参数来传递分页参数 -->
      <property name="supportMethodsArguments" value="true"/>
      <!-- 该参数默认为false -->
      <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
      <!-- 和startPage中的pageNum效果一样-->
      <property name="offsetAsPageNum" value="true"/>
      <!-- 该参数默认为false -->
      <!-- 设置为true时,使用RowBounds分页会进行count查询 -->
      <property name="rowBoundsWithCount" value="true"/>
      <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
      <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
      <property name="pageSizeZero" value="true"/>
      <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
      <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
      <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
      <property name="reasonable" value="true"/>
      <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
      <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
      <!-- 可配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置的用默认值 -->
      <!-- 不理解该含义的前提下,不要随便复制该配置 -->
      <property name="params" value="pageNum=start;pageSize=limit;"/>
      <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
      <property name="returnPageInfo" value="check"/>
    </plugin>
  </plugins>
</configuration>
  • 测试分页数据获取
@Test
public void test3() throws IOException {

    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
	                                                        .build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    // 设置分页相关参数 当前页 + 每页显示的条数
    PageHelper.startPage(3, 3);

    List<User> all = mapper.findAll();
    for (User user: all){
        System.out.println(user);
    }

    // 获得与分页相关的参数
    PageInfo<User> pageInfo = new PageInfo<User>(all);
    System.out.println("当前页:" + pageInfo.getPageNum());
    System.out.println("每页显示条数:" + pageInfo.getPageSize());
    System.out.println("总条数:" + pageInfo.getTotal());
    System.out.println("总页数:" + pageInfo.getPages());
    System.out.println("上一页:" + pageInfo.getPrePage());
    System.out.println("下一页:" + pageInfo.getNextPage());
    System.out.println("是否是第一个:" + pageInfo.isIsFirstPage());
    System.out.println("是否是最后一个:" + pageInfo.isIsLastPage());

    sqlSession.close();
}

# 代理dao层

Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同Dao接口实现类方法一致

Mapper 接口开发需要遵循以下规范:

  • Mapper.xml文件中的namespace与mapper接口的全限定名相同
  • Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
  • Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
  • Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
<?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="com.itheima.mapper.UserMapper">
    <select id="findAll" resultType="user">
        select * from user
    </select>
	<select id="findById" parameterType="int" resultType="user">
	    select * from user where id = #{id}
	</select>
</mapper>
public interface UserMapper{
	public List<User> findAll();
	public User findById(int id);
}
@Test
public void proxyTest() throws IOException {
	InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

	SqlSession sqlSession = sqlSessionFactory.openSession();
	
	//获得MyBatis框架生成的UserMapper接口的实现类
	UserMapper mapper = sqlSession.getMapper(UserMapper.class);
	
	List<User> userList = mapper.findAll();
	User user = mapper.findById(1);
}

# 多参数(@Param)

不需要使用arg0、arg1、param1、param2等等,直接使用@Param注解增强可读性

@Mapper
public interface StudentMapper{
    /**
     * 这是多参数查询
     * 根据name和sex查询Student信息
     * 如果是多个参数的话,MyBatis框架底层的做法如下:
     *     MyBatis框架会自动创建一个Map集合并且Map集合是以这种方式存储参数的
     *          map.put("arg0",name);/map.put("param1",name);
     *          map.put("arg1",sex);/map.put("param2",sex);
     *          
     * 使用Param注解指定Sql语句中的#{}命名
     * @param name
     * @param sex
     * @return
     */
    List<Student> selectByNameAndSex(
            @Param("nnn") String name,
            @Param("sss") Character sex);
}
<select id="selectByNameAndSex" resultType="student">
   select id,name,age from student where name = #{nnn} and sex = #{sss}
</select>

注意

当传递的是普通参数时,需要使用 #{} 的方式,而当传递的是 SQL 命令或 SQL 关键字时,需要使用 ${} 来对 SQL 中的参数进行直接替换并执行

# 动态SQL

动态 SQL 是 MyBatis 的强大特性之一,可以通过根据不同条件拼接 SQL 语句

# if的使用

<select id="findByIdUsername" parameterType="user" resultType="user">
  SELECT * FROM user
  <where>
    <if test="id != 0">
	    id=#{id}
	</if>
    <if test="username!=null">
	    AND username=#{username}
	</if>
  </where>
</select>

if 标签 test 等于判断

//当使用==的时候,需要test外层使用 ''单引号,里面的参数使用 ""双引号!
<if test='flag != null and flag == "3"'>

# foreach的使用

当循环执行sql的拼接操作,可以使用 foreach。例如:SELECT * FROM USER WHERE id IN (1,2,3)

<!--list-->
<select id="findByIds" parameterType="list" resultType="user">
	select * from user
	<where>
		<foreach collection="list" open="id in(" close=")" item="id" separator=",">
			#{id}
		</foreach>
	</where>
</select>

<!--map-->
<select id="findByIds" parameterType="map">
   select * from user
   <where>
      <foreach item="id" collection="ids" open="id in (" close=")" separator=",">
	     #{id}
	  </foreach>
   </where>
</select>

<!--array-->
<select id="findByIds" parameterType="String">
   select * from user
   <where>
      <foreach item="id" collection="array" open="id in (" close=")" separator=",">
	     #{id}
	  </foreach>
   </where>
</select>

<!--批量插入-->
<insert id="batchSave" parameterType="java.util.List">
    insert into user_info(id,username,address,age) values
    <foreach collection="list" item="user" separator=",">
        (#{user.id},#{user.username},#{user.address},#{user.age})
    </foreach>
</insert>

# include的使用

当Sql语句有许多重复的地方时,可以使用sql将其中可将重复的sql提取出来,使用时用include引用即可

<sql id="selectAll">select * from User</sql>

<select id="findById" parameterType="int" resultType="user">
	<include refid="selectAll"></include> 
	where id=#{id}
</select>

<select id="findByIds" parameterType="list" resultType="user">
	<include refid="selectAll"></include> 
	<where>
		<foreach collection="array" open="id in(" close=")" item="id" separator=",">
			#{id}
		</foreach>
	</where>
</select>

# 多表操作

# 一对一查询

对应的 sql 语句:select * from orders o,user u where o.uid=u.id

  • 创建Order和User实体
//Order
public class Order {
	private int id;
	private Date ordertime;
	private double total;
	
	//当前订单属于哪个客户
	private User user;
}

//User
public class User {
	private int id;
	private String username;
	private String password;
	private Date birthday;
}
  • 创建OrderMapper接口
public interface OrderMapper {
	List<Order> findAll();
}
  • 配置映射文件OrderMapper.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="com.lzjtu.mapper.OrderMapper">

    <resultMap id="orderMap" type="order">
		<!-- 手动指定字段与实体属性的映射关系
			 column: 数据表的字段名称
			 property: 实体的属性名称
		-->
        <id column="oid" property="id"></id>
        <result column="ordertime" property="ordertime" />
        <result column="total" property="total" />
		<!--<result column="uid" property="user.id" />-->
		<!--<result column="username" property="user.username" />-->
		<!--<result column="password" property="user.password" />-->
		<!--<result column="birthday" property="user.birthday" />-->
        <!--
            property: 当前实体(order) 中的属性名称
            javaType: 当前实体(order) 中的属性类型(User)
         -->
        <association property="user" javaType="user">
            <id column="uid" property="id" />
            <result column="username" property="username" />
            <result column="password" property="password" />
			<result column="birthday" property="birthday" />
        </association>
    </resultMap>
    
    <!--查询操作-->
    <select id="findAll" resultMap="orderMap">
        select *,o.id oid from user2 u,orders o where u.id=o.uid;
    </select>
</mapper>
  • 核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
                            "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--通过properties标签加载外部properties文件-->
    <properties resource="jdbc.properties" />
    
    <typeAliases>
        <typeAlias type="com.lzjtu.domain.User" alias="user"></typeAlias>
        <typeAlias type="com.lzjtu.domain.Order" alias="order"></typeAlias>
        <typeAlias type="com.lzjtu.domain.Role" alias="role"></typeAlias>
    </typeAliases>
	<!--数据源环境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
	<!--加载映射文件-->
    <mappers>
        <mapper resource="com/lzjtu/mapper/UserMapper.xml"></mapper>
        <mapper resource="com/lzjtu/mapper/OrderMapper.xml"></mapper>
    </mappers>
</configuration>

# 一对多查询

对应的sql语句:select *,o.id oid from user u left join orders o on u.id=o.uid

  • 创建Order和User实体
//Order
public class Order {
	private int id;
	private Date ordertime;
	private double total;
}

//User
public class User {
	private int id;
	private String username;
	private String password;
	private Date birthday;
	
	//当前用户具备哪些订单
	private List<Order> orderList;
}
  • 创建UserMapper接口
public interface UserMapper {
	List<User> findAll();
}
  • 配置映射文件UserMapper.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="com.lzjtu.mapper.UserMapper">

    <resultMap id="userMap" type="user">
		<!-- 手动指定字段与实体属性的映射关系
			 column: 数据表的字段名称
			 property: 实体的属性名称
		-->
        <id column="id" property="id"></id>
		<result column="username" property="username" />
		<result column="password" property="password" />
		<result column="birthday" property="birthday" />
		<!--配置结合信息
		    property: 实体类中集合的名称
		    ofType: 当前集合中的数据类型
		 -->
		<collection property="orderList" ofType="order">
		        <!--封装order的数据-->
				<id column="oid" property="id" />
				<result column="ordertime" property="ordertime" />
				<result column="total" property="total" />
		</collection>
    </resultMap>
    
    <!--查询操作-->
    <select id="findAll" resultMap="orderMap">
        select *,o.id oid from user u left join orders o on u.id=o.uid;
    </select>
</mapper>
  • 核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
                            "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--通过properties标签加载外部properties文件-->
    <properties resource="jdbc.properties" />
    
    <typeAliases>
        <typeAlias type="com.lzjtu.domain.User" alias="user"></typeAlias>
        <typeAlias type="com.lzjtu.domain.Order" alias="order"></typeAlias>
        <typeAlias type="com.lzjtu.domain.Role" alias="role"></typeAlias>
    </typeAliases>
	<!--数据源环境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
	<!--加载映射文件-->
    <mappers>
        <mapper resource="com/lzjtu/mapper/UserMapper.xml"></mapper>
        <mapper resource="com/lzjtu/mapper/OrderMapper.xml"></mapper>
    </mappers>
</configuration>

# 多对多查询

需要引入中间表,对应的sql语句:
select u.,r.,r.id rid from user u left join user_role ur on u.id=ur.user_idinner join role r on ur.role_id=r.id

  • 创建实体
//User
public class User {
	private int id;
	private String username;
	private String password;
	private Date birthday;
	
	//当前用户具备哪些角色
	private List<Role> roleList;
}

//Role
public class Role {
	private int id;
	private String rolename;
	private String roledesc;
}
  • 创建UserMapper接口
public interface UserMapper {
	List<User> findAllRole();
}
  • 配置映射文件UserMapper.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="com.lzjtu.mapper.UserMapper">

    <resultMap id="userRoleMap" type="user">
		<!-- 手动指定字段与实体属性的映射关系
			 column: 数据表的字段名称
			 property: 实体的属性名称
		-->
        <id column="id" property="id"></id>
		<result column="username" property="username" />
		<result column="password" property="password" />
		<result column="birthday" property="birthday" />
		<!--配置结合信息
		    property: 实体类中集合的名称
		    ofType: 当前集合中的数据类型
		 -->
		<collection property="roleList" ofType="role">
		        <!--封装order的数据-->
				<id column="rid" property="id" />
				<result column="rolename" property="rolename" />
				<result column="roledesc" property="roledesc" />
		</collection>
    </resultMap>
    
    <!--查询操作-->
    <select id="findAllRole" resultMap="userRoleMap">
        select u.*,r.*,r.id rid from user u left join user_role ur on u.id=ur.user_id 
		inner join role r on ur.role_id=r.id;
    </select>
</mapper>
  • 核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
                            "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--通过properties标签加载外部properties文件-->
    <properties resource="jdbc.properties" />
    
    <typeAliases>
        <typeAlias type="com.lzjtu.domain.User" alias="user"></typeAlias>
        <typeAlias type="com.lzjtu.domain.Order" alias="order"></typeAlias>
        <typeAlias type="com.lzjtu.domain.Role" alias="role"></typeAlias>
    </typeAliases>
	<!--数据源环境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
	<!--加载映射文件-->
    <mappers>
        <mapper resource="com/lzjtu/mapper/UserMapper.xml"></mapper>
        <mapper resource="com/lzjtu/mapper/OrderMapper.xml"></mapper>
    </mappers>
</configuration>

# 注解开发

# 常用注解

Mybatis也可以使用注解开发方式,这样我们就可以减少编写Mapper映射文件了

@Insert //实现新增
@Update //实现更新
@Delete //实现删除
@Select //实现查询
@Result //实现结果集封装
@Results //可以与@Result 一起使用,封装多个结果集
@One //实现一对一结果集封装
@Many //实现一对多结果集封装

# 简单的增删改查

@Insert("insert into user2 values(#{id},#{username},#{password},#{birthday})")
public void save(User user);

@Select("select * from user2 where id=#{id}")
public User findById(int id);

@Select("select * from user2")
public List<User> findAll();

@Update("update user2 set usernme=${username}, password=#{password} where id=#{id}")
public void update(User user);

@Delete("delete from user2 where id=#{id}")
public void delete(int id);

修改MyBatis的核心配置文件

<mappers>
    <!--扫描使用注解的类所在的包-->
    <package name="com.itheima.mapper"></package>
</mappers>

# 复杂的映射开发

实现复杂关系映射之前我们可以在映射文件中通过配置 resultMap 来实现,使用注解开发后,我们可以使用@Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置

# 一对一查询

对应的sql语句:

select * from orders;
select * from user where id=查询出订单的uid;
  • 创建Order和User实体
//Order
public class Order {
	private int id;
	private Date ordertime;
	private double total;
	
	//当前订单属于哪个客户
	private User user;
}

//User
public class User {
	private int id;
	private String username;
	private String password;
	private Date birthday;
}
  • 创建OrderMapper接口
public interface OrderMapper {
	List<Order> findAll();
}
  • 使用注解配置Mapper
//OrderMapper.java
@Select("select * from orders")
@Results({
        @Result(column = "id", property = "id"),
        @Result(column = "ordertime", property = "ordertime"),
        @Result(column = "total", property = "total"),
        @Result(
                property = "user", // 要封装的实体类中的属性名称
                column = "uid", // 根据那个字段取查询user表的数据
                javaType = User.class, // 要封装的实体类型
                // select属性 代表查询那个接口的方法获得数据
                one = @One(select = "com.lzjtu.mapper.UserMapper.findById")
        )
})
public List<Order> findAll();

//UserMapper.java
@Select("select * from user2 where id=#{id}")
public User findById(int id);

//另一种写法
@Select("select *,o.id oid from orders o, user2 u where o.uid=u.id")
@Results({
        @Result(column = "oid", property = "id"),
        @Result(column = "ordertime", property = "ordertime"),
        @Result(column = "total", property = "total"),
        @Result(column = "uid", property = "user.id"),
        @Result(column = "username", property = "user.username"),
        @Result(column = "password", property = "user.password")
})
public List<Order> findAll();

# 一对多查询

对应的sql语句:

select * from user;
select * from orders where uid=查询出用户的id;
  • 创建Order和User实体
//Order
public class Order {
	private int id;
	private Date ordertime;
	private double total;
}

//User
public class User {
	private int id;
	private String username;
	private String password;
	private Date birthday;
	
	//当前用户具备哪些订单
	private List<Order> orderList;
}
  • 创建UserMapper接口
public interface UserMapper {
	List<User> findAll();
}
  • 使用注解配置Mapper
//UserMapper.java
@Select("select * from user2")
@Results({
        @Result(id=true, column = "id", property = "id"),
        @Result(column = "username", property = "username"),
        @Result(column = "password", property = "password"),
        @Result(
                property = "orderList",
                column = "id",
                javaType = List.class,
                many = @Many(select = "com.lzjtu.mapper.OrderMapper.findByUid")
        )

})
public List<User> findUserAndOrderAll();

//OrderMapper.java
@Select("select * from orders where id=#{uid}")
public List<Order> findByUid(int uid);

# 多对多查询

跟一对多写法差不多(除了查询语句),对应的sql语句:

select * from user;
select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=用户的id
  • 创建实体
//User
public class User {
	private int id;
	private String username;
	private String password;
	private Date birthday;
	
	//当前用户具备哪些角色
	private List<Role> roleList;
}

//Role
public class Role {
	private int id;
	private String rolename;
	private String roledesc;
}
  • 创建UserMapper接口
public interface UserMapper {
	List<User> findAllRole();
}
  • 使用注解配置Mapper
//UserMapper.java
@Select("select * from user")
@Results({
         @Result(id = true, column = "id", property = "id"),
         @Result(column = "username", property = "username"),
         @Result(column = "password", property = "password"),
         @Result(
                 property = "roleList",
                 column = "id",
                 javaType = List.class,
                 many = @Many(select = "com.lzjtu.mapper.RoleMapper.findByUid")
         )
 })
public List<User> findUserAndRoleAll();

//RoleMapper.java
@Select("select * from sys_user_role ur,sys_role r where ur.roleId=r.id and ur.userId=#{id}")
public List<Role> findByUid(int id);

ResultMap和ResultType的区别

resultType:数据库中的字段和pojo中的字段必须完全一致 resultMap:通常需要在mapper.xml中定义resultMap进行pojo和相应表字段的对应

# spring整合mybatis(注解的方式)

# 导入Maven坐标

<dependencies>
  <!--spring相关-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.5.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.0.5.RELEASE</version>
  </dependency>
  
  <!--mybatis相关-->
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.5</version>
  </dependency>
  <!--mybatis整合spring-->
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.1</version>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.6</version>
  </dependency>
 <dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid</artifactId>
   <version>1.1.16</version>
 </dependency>
</dependencies>
  • 业务逻辑层AccountService接口、AccountServiceImpl类
//AccountService接口
package com.itheima.service;

import com.itheima.domain.Account;
import java.util.List;
 
public interface AccountService {
 
    void save(Account account);
 
    void delete(Integer id);
 
    void update(Account account);
 
    List<Account> findAll();
 
    Account findById(Integer id);
 
}


//AccountServiceImpl类
package com.itheima.service.impl;
 
import com.itheima.dao.AccountDao;
import com.itheima.domain.Account;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class AccountServiceImpl implements AccountService {
    // 自动装配 处理依赖关系 private AccountDao accountDao = new AccountDao();
    @Autowired 
    private AccountDao accountDao;
 
    public void save(Account account) {
        accountDao.save(account);
    }
 
    public void update(Account account){
        accountDao.update(account);
    }
 
    public void delete(Integer id) {
        accountDao.delete(id);
    }
 
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }
 
    public List<Account> findAll() {
        return accountDao.findAll();
    }
}
  • 数据操作层AccountDao
package com.itheima.dao;
 
import com.itheima.domain.Account;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
 
public interface AccountDao {
 
    @Insert("insert into tbl_account(name,money)values(#{name},#{money})")
    void save(Account account);
 
    @Delete("delete from tbl_account where id = #{id} ")
    void delete(Integer id);
 
    @Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ")
    void update(Account account);
 
    @Select("select * from tbl_account")
    List<Account> findAll();
 
    @Select("select * from tbl_account where id = #{id} ")
    Account findById(Integer id);
}
  • java代码代替spring的xml配置文件
package com.itheima.config;
 
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
 
@Configuration  //相当于spring xml配置文件中的总体标签  <beans></beans>
@ComponentScan("com.itheima")   //扫描该包下是否有注解bean的类
@PropertySource({"jdbc.properties"})  //配置加载jdbc.properties文件 使用$符获取数据
@Import({JdbcConfig.class,MybatisConfig.class})  //导入2个配置
public class SpringConfig {
 
}
  • 连接数据库的信息 jdbcConfig
package com.itheima.config;
 
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
 
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
 
    @Bean
    public DataSource dataSource(){
 
        // 1、new 第三方bean/对象
        DruidDataSource dataSource =new DruidDataSource();
 
        // 2、给第三方bean/对象属性赋值
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
 
        // 3、把第三方bean/对象返回给该方法
        return dataSource;
    }
}
  • spring整合mybatis的核心配置文件 MybatisConfig
package com.itheima.config;
 
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;

public class MybatisConfig {
   /*
    <properties resource="jdbc.properties"></properties> 
    <typeAliases>
	    <!-- 加载完sql映射文件后,是以该包下的POJO对象进行返回数据的 -->
        <package name="com.itheima.domain"/>    
    </typeAliases>
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"></property>
                <property name="url" value="${jdbc.url}"></property>
                <property name="username" value="${jdbc.username}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </dataSource>
        </environment>
    </environments>
    */
 
    
    // 该方法new出来的SqlSessionFactoryBean对象相当于整合mybatis的核心配置文件中的上面那些信息
    @Bean
	// 传参的目的就是处理引用型依赖关系拿到DataSource对象
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){  
        // spring在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法
		// SqlSessionFactoryBean有getObject()方法
        SqlSessionFactoryBean sqfb =new SqlSessionFactoryBean();
        // 整合typeAliases
        sqfb.setTypeAliasesPackage("com.itheima.domain");
		// 整合jdbc连接信息
        sqfb.setDataSource(dataSource); 
		//添加分页支持
        Interceptor interceptor = new PageInterceptor();
        Properties properties = new Properties();
        properties.setProperty("helperDialect", "mysql");
        properties.setProperty("offsetAsPageNum", "true");
        properties.setProperty("rowBoundsWithCount", "true");
        properties.setProperty("reasonable", "true");
        properties.setProperty("supportMethodsArguments", "true");
        interceptor.setProperties(properties);
        sqfb.setPlugins(new Interceptor[]{interceptor});
		
        return sqfb;
    }
 
    /*
    <mappers>
        <!-- 加载sql映射文件 -->
        <package name="com.itheima.dao"></package>
    </mappers>
     */
	
	// 该方法new出来的MapperScannerConfigurer对象相当于整合mybatis的核心配置文件中的上面那些信息
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        // 1、new MapperScannerConfigurer对象
        MapperScannerConfigurer mp =new MapperScannerConfigurer();
        // 2、整合mybatis的核心配置信息
        mp.setBasePackage("com.itheima.dao");
        // 因为这里我们是用代理接口注解形式做的处理数据库的,也就是说没有用UserMapper.xml形式
        // 所以直接加载到dao层就行了,如果有xml核心配置文件的话,就加载到核心配置文件
        return mp;
    }
}

# MyBatis Plus

# 基本介绍

MybatisPlus(简称MP)是在MyBatis基础上开发的增强型工具,旨在简化开发、提供效率。
我们虽然使用MP但是底层依然是MyBatis的东西,也就是说我们也可以在MP中写MyBatis的内容。
官方网站 (opens new window)

# 快速入门

  • 创建数据库及表
create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
CREATE TABLE user (
    id bigint(20) primary key auto_increment,
    name varchar(32) not null,
    password  varchar(32) not null,
    age int(3) not null ,
    tel varchar(32) not null
);

insert into user values(1,'Tom','tom',3,'18866668888');
insert into user values(2,'Jerry','jerry',4,'16688886666');
insert into user values(3,'Jock','123456',41,'18812345678');
insert into user values(4,'传智播客','itcast',15,'4006184000');
  • 创建SpringBoot工程,勾选配置使用技术,只使用mysql,不用添加Mybatis,因为导入的MybatisPlus依赖,会根据依赖传递自动导入Mybatis、Mybatis-spring的依赖
  • pom.xml添加MybatisPlus和druid依赖,由于MP并未被收录到idea的系统内置配置,无法直接选择加入,需要手动在pom.xml中配置添加
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>
<!--druid数据源可以加也可以不加,SpringBoot有内置的数据源,可以配置成使用Druid数据源-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>
  • 添加相关配置信息
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    # UTC是标准时区,和咱们的时间差8小时,所以可以将其修改为Asia/Shanghai
    url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC 
    username: root
    password: root
  • 根据数据库表创建实体类
//id类型不是int,而是bigint(20),因为MybatisPlus默认使用雪花算法自增id,例 1561681377489510402L
public class User {   
    private Long id;    //注意id类型是Long
    private String name;
    private String password;
    private Integer age;
    private String tel;
    //setter...getter...toString方法略,别忘了自己生成
}


//注解了lombok的@Data会自动生成getter,setter,toString方法
@Data
//一般数据库表名tbl_user,这里注解@TableName("tbl_user")
@TableName("tbl_user")
public class User {
    //设置主键自增策略为auto,mp默认自增策略是ASSIGN_ID,雪花算法。也可以在yml中全局配置
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    //value属性起别名,select设置该字段是否参与查询,针对于一些密码等隐私数据不希望被查出来
    @TableField(value = "password",select = false)
    private String password;
    private Integer age;
 
    private String tel;
    //exist属性设置是否在数据库中存在该字段
    @TableField(exist = false)
    private String online;
    //乐观锁注解版本,需要搭配乐观拦截器
    @Version
    private Integer version;
    //逻辑删除,本质是更新,数据库内该字段默认是0,通过标记为1来判定删除。
    @TableLogic
    private Integer delete;
}
  • 创建Dao接口,继承基础mapper:BaseMapper< User >
@Mapper
public interface UserDao extends BaseMapper<User>{
	
}
  • Service层IService以及ServiceImpl(一般不使用)
//Service接口
public interface BaseProcedureService extends IService<BaseProcedure> {
	void testInsert()
}

//ServiceImpl类
@Service
public class BaseProcedureServiceImpl extends ServiceImpl<BaseProcedureMapper,BaseProcedure> implements BaseProcedureService {

    public void testInsert(){
        BaseProcedure result = this.getById("测试");
        System.out.println("result:{}"+result);
    }
}
  • 编写引导类,Dao接口要想被容器扫描到,有两种解决方案
    • 在每个Dao接口上添加@Mapper注解,并且确保Dao处在引导类所在包或其子包中
    • 在引导类上添加@MapperScan注解,其属性为所要扫描的Dao所在包,该方案的好处是只需写一次
@SpringBootApplication
//@MapperScan("com.itheima.dao")
public class Mybatisplus01QuickstartApplication {
    public static void main(String[] args) {
        SpringApplication.run(Mybatisplus01QuickstartApplication.class, args);
    }
 
}
  • 编写测试类
@SpringBootTest
class MpDemoApplicationTests {
 
	@Autowired
	private UserDao userDao;
	
	@Test
	public void testGetAll() {
		List<User> userList = userDao.selectList(null);
		System.out.println(userList);
	}
}

# 功能使用

# 五种主键自动生成策略

  • AUTO策略:数据库默认自增策略
  • NONE: 不设置id生成策略
  • INPUT:用户手工输入id,如果id为null会报错
  • ASSIGN_ID:雪花算法生成id(可兼容数值型与字符串型),mp默认id策略
  • ASSIGN_UUID:以UUID生成算法作为id生成策略

其他的几个策略均已过时,都将被ASSIGN_ID和ASSIGN_UUID代替掉

# 雪花算法

雪花算法(SnowFlake),是Twitter官方给出的算法实现 是用Scala写的。其生成的结果是一个64bit大小整数,它的结构如下图:

雪花算法

  • 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
  • 41bit-时间戳,用来记录时间戳,毫秒级
  • 10bit-工作机器id,用来记录工作机器id,其中高位5bit是数据中心ID其取值范围0-31,低位5bit是工作节点ID其取值范围0-31,两个组合起来最多可以容纳1024个节点
  • 序列号占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID

# 标准CRUD使用

  • 新增操作
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
 
    @Autowired
    private UserDao userDao;
 
    @Test
    void testSave() {
        User user = new User();
        user.setName("黑马程序员");
        user.setPassword("itheima");
        user.setAge(12);
        user.setTel("4006184000");
        userDao.insert(user);
    }
}
  • 删除操作
 @SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
 
    @Autowired
    private UserDao userDao;
 
    @Test
    void testDelete() {
        //Long类型后面都有L字母,不加会溢出、报错,即使值为1也要加,即1L
        userDao.deleteById(1401856123725713409L);
		
		//根据map删除
		Map<String, Object> columnMap = new HashMap<>();
		columnMap.put("age",20);
		columnMap.put("name","张三");
		//将columnMap中的元素设置为删除的条件,多个之间为and关系
		int result = userDao.deleteByMap(columnMap);
		System.out.println("result = " + result);
    }
}
  • 修改操作
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
 
    @Autowired
    private UserDao userDao;
 
    @Test
    void testUpdate() {
        User user = new User();
        user.setId(1L);
        user.setName("Tom888");
        user.setPassword("tom888");
        userDao.updateById(user);
		
		User user = new User();
		user.setAge(22); //更新的字段
		//更新的条件
		QueryWrapper<User> wrapper = new QueryWrapper<>();
		wrapper.eq("id", 6);
		int result = this.userMapper.update(user, wrapper);
		System.out.println("result = " + result);
    }
}
  • 根据ID查询
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
 
    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetById() {
        User user = userDao.selectById(2L);
    }
}
  • 查询所有
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
 
    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll() {
        List<User> userList = userDao.selectList(null);
        System.out.println(userList);
    }
}

# 分页功能

IPage是一个接口,我们需要用它的实现类Page来构建它

使用了两种方法实现:

  • 简单分页查询:用条件构造器QueryWrapper就能够实现
  • 复杂分页查询:就必须自己手写mapper文件:联表查询
@RestController
public class UserController {

    @Autowired
    private UserService userService;
    
	 /**
     * <p>
     * 查询 : 根据gender性别查询用户列表,分页显示
     * </p>
     *
     * @param page 指定当前页码1 ,每页显示2行
     * @param state 状态
     * @return 分页对象
     */
    @RequestMapping("/{gender}")
    public void test(@PathVariable Integer gender) {
		// 模拟复杂分页查询
        IPage<UserEntity> userEntityIPage = userService.selectUserByGender(new Page<>(1, 2), gender);
        System.out.println("总页数: " + userEntityIPage.getPages());
        System.out.println("总记录数: " + userEntityIPage.getTotal());
        userEntityIPage.getRecords().forEach(System.out::println);
		// 简单分页查询
        QueryWrapper<UserEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("gender", gender);
        userService.pageByGender(new Page<>(1, 2), queryWrapper);
        System.out.println("总页数: " + userEntityIPage.getPages());
        System.out.println("总记录数: " + userEntityIPage.getTotal());
        userEntityIPage.getRecords().forEach(System.out::println);
    }
}


//UserService.java
@Service
public class UserService{

    @Autowired
    private UserMapper userMapper;

	// 模拟复杂分页查询
    public IPage<UserEntity> selectUserByGender(Page<UserEntity> page,Integer gender){
        return userMapper.selectUserByGender(page,gender);
    }
	
	// 简单分页查询:条件构造器QueryWrapper,不涉及到啥复杂的处理,直接调用DAO层
    public IPage<UserEntity> pageByGender(Page<UserEntity> page, QueryWrapper<UserEntity> queryWrapper){
        return userMapper.selectPage(page,queryWrapper);
    }
}

//UserMapper.java
@Mapper
public interface UserMapper extends BaseMapper<UserEntity> {

	// 通过性别分页查询
    IPage<UserEntity> selectUserByGender(Page<UserEntity> page, @Param("gender") Integer gender);
}

//UserMapper.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="com.hyl.demo.mapper.UserMapper">
    <sql id = "entity">
        a.id,a.username,a.password,a.gender,a.age,a.create_time
    </sql>
    <select id="selectUserByGender" resultType="com.hyl.demo.entity.UserEntity">
        select <include refid="entity"/> from user a
        <where>
            <if test="gender != null">
                and a.gender = #{gender}
            </if>
        </where>
    </select>
</mapper>
  • 在config包下创建分页拦截器类
//注解为配置类@Configuration,也可以在引导类@Import({MybatisPlusConfig.class})
@Configuration
public class MybatisPlusConfig {
    //被Spring容器管理    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //1 创建mp拦截器对象MybatisPlusInterceptor
        MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
        //2 添加内置拦截器,参数为分页内置拦截器对象PaginationInnerInterceptor
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }
}
  • 查看MybatisPlus标准日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台

mybatis打印日志

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台

# 条件查询

Wrapper类就是用来构建查询条件的
包装器Wrapper是接口,实际开发中主要使用它的两个实现类:QueryWrapper和LambdaQueryWrapper

  • 基本比较操作
eq:等于 =
ne:不等于 <>

alleq:全部eq(或个别isNull)
例子:allEq({id:1,name:"老王",age:null})--->id = 1 and name = '老王' and age is null 

gt:大于 >
ge:大于等于 >=

lt:小于 <
le:小于等于 <=

between:BETWEEN 值1 AND 值2
notBetween:NOT BETWEEN 值1 AND 值2

in:字段 IN (value.get(0), value.get(1), ...) 
notIn:字段 NOT IN (v0, v1, ...)

like():前后加百分号,如 %J%
likeLeft():左边加百分号,如 %J
likeRight():后面加百分号,如 J%
  • 查询包装器QueryWrapper
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
 
    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
        //创建QueryWrapper对象
        QueryWrapper qw = new QueryWrapper();
        //lt代表小于,大于是gt
        qw.lt("age",18);
        List<User> userList = userDao.selectList(qw);
        System.out.println(userList);
    }
}
  • QueryWrapper的基础上使用lambda
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
 
    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
        //使用Lambda,QueryWrapper<User>必须加泛型
        QueryWrapper<User> qw = new QueryWrapper<User>();
        qw.lambda().lt(User::getAge, 10);//添加条件,使用Lambda不容易写错属性名
        List<User> userList = userDao.selectList(qw);
        System.out.println(userList);
    }
}
  • LambdaQueryWrapper(推荐使用)
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
 
    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.lt(User::getAge, 10);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}
  • 多条件查询默认是and,or要用.or()
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
 
    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
		//lqw.lt(User::getAge, 30);
		//lqw.gt(User::getAge, 10);
		//链式编程
        lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}
  • 条件查询null值处理

在domain.query下新建一个模型类,让其继承User类,并在其中添加age2属性,UserQuery在拥有User属性后同时添加了age2属性。

@Data
public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
}
 
@Data
public class UserQuery extends User {
    private Integer age2;
}

//MP给我们提供了简化方式实现区间条件查询
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
 
    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
        //模拟页面传递过来的查询数据
        UserQuery uq = new UserQuery();
        uq.setAge(10);
        uq.setAge2(30);
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        //第一个参数为判断条件
        lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());
        lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge());
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}

# 查询指定字段

目前我们在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容

//方法一(推荐):使用Lambda
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
 
    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.select(User::getId,User::getName,User::getAge);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}

//方法二(不建议):不用lambda
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
 
    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
        QueryWrapper<User> lqw = new QueryWrapper<User>();
        lqw.select("id","name","age","tel");
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}

# 聚合查询

聚合查询不能用Lambda,只能用QueryWrapper

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
 
    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
        QueryWrapper<User> lqw = new QueryWrapper<User>();
        //lqw.select("count(*) as count");
        //SELECT count(*) as count FROM user
        //lqw.select("max(age) as maxAge");
        //SELECT max(age) as maxAge FROM user
        //lqw.select("min(age) as minAge");
        //SELECT min(age) as minAge FROM user
        //lqw.select("sum(age) as sumAge");
        //SELECT sum(age) as sumAge FROM user
        lqw.select("avg(age) as avgAge");
        //SELECT avg(age) as avgAge FROM user
        List<Map<String, Object>> userList = userDao.selectMaps(lqw);
        System.out.println(userList);
    }
}

# 分组查询

分组查询一定是要配合聚合函数的

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
 
    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
        QueryWrapper<User> lqw = new QueryWrapper<User>();
        lqw.select("count(*) as count,tel");
        lqw.groupBy("tel");
        List<Map<String, Object>> list = userDao.selectMaps(lqw);
        System.out.println(list);
    }
}

注意

MP只是对MyBatis的增强,如果MP实现不了,可以直接在DAO接口中使用MyBatis的方式实现

# 排序查询

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
 
    @Autowired
    private UserDao userDao;
    
    @Test
    void testGetAll(){
        LambdaQueryWrapper<User> lwq = new LambdaQueryWrapper<>();
        /**
         * condition:是否进行排序,当condition为true,进行排序,如果为false,则不排序
         * isAsc:是否为升序,true为升序,false为降序
         * columns:需要操作的列
         */
        lwq.orderBy(true,false, User::getId);
 
        userDao.selectList(lw
    }
}

# 多记录操作

@SpringBootTest
class Mybatisplus03DqlApplicationTests {
 
    @Autowired
    private UserDao userDao;
	
    @Test
    void testDelete(){
        //删除指定多条数据
        List<Long> list = new ArrayList<>();
        list.add(1402551342481838081L);
        list.add(1402553134049501186L);
        list.add(1402553619611430913L);
        userDao.deleteBatchIds(list);
		
		//查询指定多条数据
		List<Long> list = new ArrayList<>();
		list.add(1L);
		list.add(3L);
		list.add(4L);
		userDao.selectBatchIds(list);
    }
}

# 逻辑删除

逻辑删除底层其实是更新操作

  • 修改数据库表添加deleted列
  • 实体类添加属性
  • 实体类设置逻辑删除成员
    • 单个实体类注解@TableLogic
    @Data
    //@TableName("tbl_user") 可以不写是因为配置了全局配置
    public class User {
        @TableId(type = IdType.ASSIGN_UUID)
        private String id;
        private String name;
        @TableField(value="pwd",select=false)
        private String password;
        private Integer age;
        private String tel;
        @TableField(exist=false)
        private Integer online;
        @TableLogic(value="0",delval="1")
        //value属性是默认值,delval是删除后修改的值
        private Integer deleted;
    }
    
    • yml全局配置
    mybatis-plus:
      global-config:
        db-config:
           逻辑删除字段名
          logic-delete-field: deleted
           逻辑删除字面值:未删除为0
          logic-not-delete-value: 0
           逻辑删除字面值:删除为1
          logic-delete-value: 1
    

注意

逻辑删除后的查询操作,只会查询where deleted=0的数据

# 乐观锁和悲观锁

  • 乐观锁通过版本号控制事务的并发
  • 悲观锁:就是锁定使用的资源,其他请求想要使用这个资源就必须排队,等这个资源释放了才能继续

这种方式是针对于小型企业的解决方案,因为数据库本身的性能就是个瓶颈,如果对其并发量超过2000以上的就需要考虑其他的解决方案了

乐观锁的实现方式:

  • 数据库表添加列version,默认值1
  • 在模型类中添加对应的属性,@Version
@Data
//@TableName("tbl_user") 可以不写是因为配置了全局配置
public class User {
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;
    private String name;
    @TableField(value="pwd",select=false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)
    private Integer online;
    private Integer deleted;
    @Version
    private Integer version;
}
  • 添加乐观锁的拦截器
@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mpInterceptor() {
        //1.定义Mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        //2.添加乐观锁拦截器
        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
         //分页拦截器 
        //mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());   
        return mpInterceptor;
    }
}
  • 先查询version再更新操作

要想实现乐观锁,首先第一步应该是拿到表中的version,然后拿version当条件在将version加1更新回到数据库表中,所以我们在修改的时候,需要对其进行查询

@SpringBootTest
class Mybatisplus03DqlApplicationTests {
 
    @Autowired
    private UserDao userDao;
	
    @Test
    void testUpdate(){
		
        //先通过要修改的数据id将当前数据查询出来
        User user1 = userDao.selectById(3L); //version=1
		User user2 = userDao.selectById(4L); //version=1
		
        user2.setName("Jock888");
        userDao.updateById(user2);           //version=2
		
        user1.setName("Jock666");
        userDao.updateById(user1);           //version=1修改失败
    }
}

# 多表连接查询

mybatis-plus作为mybatis的增强工具,它的出现极大的简化了开发中的数据库操作,但是长久以来,它的联表查询能力一直被大家所诟病。一旦遇到left join或right join的左右连接,你还是得老老实实的打开xml文件,手写上一大段的sql语句。

mybatis-plus-join (opens new window) 使用步骤:

  • 在pom中添加 mybatis plus join依赖
<!-- mpj 依赖 -->
<dependency>
	<groupId>com.github.yulichang</groupId>
    <artifactId>mybatis-plus-join-boot-starter</artifactId>
    <version>1.4.5</version>
</dependency>
<!-- mp 依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>
  • 添加两个数据库实体类 User 和 Address 和结果类 UserDTO
@Data
@ToString
@TableName("area")
public class User {
	@TableId
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

@Data
@ToString
@TableName("address")
public class Address {
	@TableId
    private Long id;
    private Long userId;
    private String city;
    private String address;
}

@Data
@ToString
public class UserDTO{
    private Long id;
    private String name;
    private Integer age;
    private String email;

	//address关联表中的两个字段
    private String city;
    private String address;
    
	//地址列表 用于接下来的一对多映射查询
	private List<Address> addressList;

	//地址 用于接下来的一对一映射查询
	private Address address;
}
  • 添加mapper并且继承MPJBaseMapper
@Mapper
public interface UserMapper extends MPJBaseMapper<User> {

}

@Mapper
public interface AddressMapper extends MPJBaseMapper<Address> {

}
  • 实体和mapper都建好了就可以直接用了
//一对多 selectCollection
@SpringBootTest
public class SampleTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelect() {
        MPJLambdaWrapper<User> wrapper = new MPJLambdaWrapper<User>()
                .selectAll(User.class)//查询user表全部字段
                .select(Address::getCity, Address::getAddress)
                .leftJoin(Address.class, Address::getUserId, User::getId);
        List<UserDTO> userList = userMapper.selectJoinList(UserDTO.class, wrapper);
        userList.forEach(System.out::println);
    }
}

//sql 打印
SELECT t.id,t.name,t.age,t.email,t2.city,t2.address 
FROM user t LEFT JOIN address t1 ON t1.user_id = t.id


//一对一 selectAssociation
@SpringBootTest
public class SampleTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelect() {
        MPJLambdaWrapper<User> wrapper = new MPJLambdaWrapper<User>()
                .selectAll(User.class)//查询user表全部字段
                .selectAssociation(Address::getCity, UserDTO::getAddress)
                .leftJoin(Address.class, Address::getUserId, User::getId);
        List<UserDTO> userList = userMapper.selectJoinList(UserDTO.class, wrapper);
    }
}
  • 连表分页也是很常用的功能,MPJ也支持,调用selectJoinPage()就可以了
@SpringBootTest
public class SampleTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelect() {
        MPJLambdaWrapper<User> wrapper = new MPJLambdaWrapper<User>()
                .selectAll(User.class)//查询user表全部字段
                .select(Address::getCity, Address::getAddress)
                .leftJoin(Address.class, Address::getUserId, User::getId);
        Page<UserDTO> page= userMapper.selectJoinPage(new Page(1,10), UserDTO.class, wrapper);
    }
}

//sql打印
SELECT t.id,t.name,t.age,t.email,t2.city,t2.address 
FROM user t LEFT JOIN address t1 ON t1.user_id = t.id LIMIT ?

# 代码生成器

  • 导入对应的jar包mybatis-plus,druid,lombok,代码生成器mybatis-plus-generator和模板引擎velocity-engine-core依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
		                     https://maven.apache.org/xsd/maven-4.0.0.xsd">
							 
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
    </parent>
    <groupId>com.itheima</groupId>
    <artifactId>mybatisplus_04_generator</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--spring webmvc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
        <!--mybatisplus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
 
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
 
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
 
        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
 
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
 
        <!--代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>
 
        <!--velocity模板引擎-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.3</version>
        </dependency>
 
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
 
</project>
 
  • 在引导类包下,创建代码生成类CodeGenerator
public class CodeGenerator {
    public static void main(String[] args) {
        //1.获取代码生成器的对象
        AutoGenerator autoGenerator = new AutoGenerator();
 
        //创建DataSourceConfig 对象设置数据库相关配置
        DataSourceConfig dataSource = new DataSourceConfig();
        dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        autoGenerator.setDataSource(dataSource);
 
        //设置全局配置
        GlobalConfig globalConfig = new GlobalConfig();
		//设置代码生成位置
        globalConfig.setOutputDir(System.getProperty("user.dir")+"/plus/src/main/java");
        globalConfig.setOpen(false);    //设置生成完毕后是否打开生成代码所在的目录
        globalConfig.setAuthor("黑马程序员");    //设置作者
        globalConfig.setFileOverride(true);     //设置是否覆盖原始生成的文件
        globalConfig.setMapperName("%sDao");    //设置数据层接口名,%s为占位符,指代模块名称
        globalConfig.setIdType(IdType.ASSIGN_ID);   //设置Id生成策略
        autoGenerator.setGlobalConfig(globalConfig);
 
        //设置包名相关配置
        PackageConfig packageInfo = new PackageConfig();
        packageInfo.setParent("com.aaa");   //设置生成的包名
        packageInfo.setEntity("domain");    //设置实体类包名
        packageInfo.setMapper("dao");   //设置数据层包名
        autoGenerator.setPackageInfo(packageInfo);
 
        //策略设置
        StrategyConfig strategyConfig = new StrategyConfig();
        strategyConfig.setInclude("tbl_user");  //设置当前参与生成的表名,参数为可变参数
        strategyConfig.setTablePrefix("tbl_");  //设置数据库表的前缀名称
        strategyConfig.setRestControllerStyle(true);    //设置是否启用Rest风格
        strategyConfig.setVersionFieldName("version");  //设置乐观锁字段名
        strategyConfig.setLogicDeleteFieldName("deleted");  //设置逻辑删除字段名
        strategyConfig.setEntityLombokModel(true);  //设置是否启用lombok
        autoGenerator.setStrategy(strategyConfig);
        //2.执行代码生成器
        autoGenerator.execute();
    }
}

# Spring Data

# 基本介绍

Spring Data用于简化数据库访问,支持NoSQL和关系数据存储。其主要目标是使用数据库的访问变得方便快捷

官网主页 (opens new window)

当然Spring Data还有很多封装好的工具可以供我们使用,主要的就是下面这四个:

  • Spring Data JPA :减少数据访问层的开发量
  • Spring Data MongoDB:基于分布式数据层的数据库,在大数据层用的比较多
  • Spring Data Redis:开源,支持网络、内存,而且可以持久化的,提供非常多的语言支持
  • Spring Data Solr:高性能的基于Lucene的搜索功能,对查询性能优化,也有更好的扩展

# Spring Data JPA

Spring Data JPA是 Spring 基于 ORM 框架、JPA(Java Persistence API) 规范的基础上封装的一套应用框架,底层使用了 Hibernate 的 JPA 技术实现,可使开发者用极简的代码即可实现对数据的访问和操作。

# 使用流程

  • 要想在应用程序中使用Spring Data JPA,需要先引入依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
  • 在使用Spring Data JPA时,需要在实体对象上添加符合JPA规范的注解
//表明是一个实体类
@Entity
//用于指定表名
@Table(name = "account")
public class Account implements Serializable {
    //用于标识主键
	@Id
	//用于标识自增数据的
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String accountNumber;
	private String accountName;
	
	//表示account表与authority表中数据的关联关系
	@ManyToMany(targetEntity = Authority.class)
	//JoinTable注解来指定account_authority中间表,
	//joinColumns和inverseJoinColumns分别指定中间表的字段名及account和authority两张主表的外键名
	@JoinTable(name = "account_authority", joinColumns =
		@JoinColumn(name = "account_id", referencedColumnName = "id"),inverseJoinColumns = 
		@JoinColumn(name = "authority_id",referencedColumnName = "id")
	)
	private List<Authority> authorities;
}

//JPA规范中提供了one-to-one、one-to-many、many-to-one、many-to-many这4种关系
//分别处理一对一、一对多、多对一以及多对多的关联场景
@Entity          //表明是一个实体类
@Table           //对应的数据表名
@Id              //主键
@GeneratedValue  //主键生成策略
@Column          //映射表对应的字段名
@Basic           //表示该属性是表字段的映射。 如果实体的字段上没有任何注解默认就是@Basic
@Transient       //表示该属性不是表字段的映射
@Lob             //将属性映射成支持的大对象类型 ,如Clob、Blob
@IdClass         //联合主键,一般不用也不推荐用
@Temporal        //用来设置Date类型的属性映射到对应精度的字段
@Enumerated      //直接映射枚举类型的字段
  • 定义完实体对象后,再写Repository接口
@Repository
//一个继承了JpaRepository接口的空接口,实际上已经具备了访问数据库的基本CRUD功能
public interface AccountRepository extends JpaRepository<Account,Long>{
	
}
  • 构建一个AccountService并直接注入AccountRepository接口
@Service
public class AccountService {
	@Autowired
	private AccountRepository accountRepository;
	
	public Account getAccountById(Long accountId) {
		return accountRepository.getOne(accountId);
	}
}
  • 通过HTTP请求查询id为1的Account对象,可以获得如下数据
//除了account表中的账户基础数据之外,还同时获取了authority表中的用户权限数据
{
"id": 1,
"accountNumber": "Account1",
"accountName": "MyAccount",
"authorities": [
	{
	"id": 1,
	"authorityCode": " authorityCode1",
	"authorityName": " authorityName1",
	"description": "description1"
	},
	{
	"id": 2,
	"authorityCode": " authorityCode2",
	"authorityName": " authorityName2",
	"description": "description2"
	},
  ]
}

# Reponsitory接口是Spring Data的核心接口

  • Repository接口,是一个标记接口,不提供任何方法
public interface Repository<T, ID> {}
  • 如果接口继承Repository接口,该接口就会被Spring所管理
//方式一
//使用 @RepositoryDefinition 注解,并为其指定 domainClass和idClass属性
@RepositoryDefinition(domainClass=Person.class,idClass=Integer.class)
public interface PersonRepsotory{}

//方式二
public interface AccountRepository extends Repository<Account,Long>
  • Repository的子接口
CrudRepository //继承Repository,实现类CRUD相关的方法
PagingAndSortingRepository //继承CrudRepository接口,实现了分页排序的相关方法
JpaRepository //继承PagingAndSortingRepository接口,实现类JPA规范相关的方法
  • CrudRepository接口的使用
@Reponsitory
public interface EmployeeCrudRepository extends CrudRepository<Employee, Integer> {}


@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Autowired
    private EmployeeCrudRepository employeeCrudRepository;

    //根据id更新员工年龄
    @Transactional
    public void update(Integer id, Integer age) {
        employeeRepository.update(id, age);
    }

    //保存多条员工信息
    @Transactional
    public void save(List<Employee> employees) {
        employeeCrudRepository.saveAll(employees);
    }
}
  • PagingAndSortingRepository接口的使用
    • 带排序的查询findAll(Sort sort)
    • 带排序的分页查询findAll(Pageable pageable)
@Reponsitory
public interface EmployeePagingAndSortingRepository 
                 extends PagingAndSortingRepository<Employee, Integer> {}
				 


public class EmployeePagingAndSortingRepositoryTest {
    @Test
    public void testPage() {
        Pageable pageable = PageRequest.of(1,4);
        Page<Employee> page = employeePagingAndSortingRepository.findAll(pageable);
        System.out.println("总页数" + page.getTotalElements());
        System.out.println("总记录数" + page.getTotalElements());
        System.out.println("当前是:" + page.getNumber()+1);
        System.out.println("当前页面的集合" + page.getContent());
        System.out.println("当前页面的记录时" + page.getNumberOfElements());
    }
}

# 使用@Query注解实现增删改查

可以通过自定义的 JPQL 完成select、update 和 delete 操作,注意: JPQL 不支持使用 insert

  • Query注解的使用
  • 实现DELETE和UPDATE操作时候必须加上@Modifying注解和事务
  • Jpa继承的方法删除查找都是主键,非主键只有写原生sql语句
  • 在service方法上添加事务操作@Transactional
public interface EmployeeRepository extends Repository<Employee, Integer> {
    //查询最大id员工
    @Query("select o from Employee o where id = (select max(id) from Employee t1)")
    Employee getEmployeeByMaxId();

    //根据姓名和年龄查询
    @Query("select o from Employee o where o.name = ?1 and o.age = ?2")
    List<Employee> queryParams1(String name, Integer age);

    //根据姓名和年龄查询
    @Query("select o from Employee o where o.name = :name and o.age = :age")
    List<Employee> queryParams2(@Param("name") String name, @Param("age") Integer age);

    //根据名字模糊查询
    @Query("select o from Employee o where o.name like %?1%")
    List<Employee> queryLike1(String name);

    //根据名字模糊查询
    @Query("select o from Employee o where o.name like %:name%")
    List<Employee> queryLike2(@Param("name") String name);

    //统计所有员工数量
	//使用原生Sql查询:nativeQuery = true
    @Query(nativeQuery = true, value = "select count(1) from employee")
    long getCount();

    //根据id更新员工年龄
    @Modifying
    @Query("update Employee o set o.age = :age where o.id = :id")
    void update(@Param("id") Integer id, @Param("age") Integer age);
	
	//使用原生Sql实现删除
	@Transactional
	@Modifying
	@Query(value="DELETE FROM b_classuser  WHERE uo_id = ?1",nativeQuery=true)
	void deleteClassByStudent(BigDecimal uoId);
}

事务的实现

@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    //根据id更新员工年龄
    @Transactional
    public void update(Integer id, Integer age) {
        employeeRepository.update(id, age);
    }
}

# 使用方法名衍生查询

开发人员唯一要做的就是在JpaRepository接口中定义一个符合查询语义的方法

public interface EmployeeRepository extends Repository<Employee, Integer> {

    //通过名字查找雇员
    Employee findByName(String name);

    //查询名字以指定值开头且年龄小于指定值
    List<Employee> findByNameStartingWithAndAgeLessThan(String name, Integer age);

    //查询名字以指定值结束且年龄小于指定值
    List<Employee> findByNameEndingWithAndAgeLessThan(String name, Integer age);

    //查询名字在指定范围内或年龄小于指定值
    List<Employee> findByNameInOrAgeLessThan(List<String> names, Integer age);

    //查询名字在指定范围内且年龄小于指定值
    List<Employee> findByNameInAndAgeLessThan(List<String> names, Integer age);
}

# 查询条件不固定时,动态构建查询语句

可以通过继承JpaSpecification-Executor接口实现这类查询

@Repository
public interface AccountRepository extends JpaRepository<Account,Long>
                                         , JpaSpecificationExecutor<Account> {
}

对于JpaSpecificationExecutor接口而言,它背后就是Specification接口。我们可以简单理解该接口的作用就是构建查询条件。Specification接口的核心方法只有一个

public interface Specification<T> extends Serializable {
	//Root对象代表所查询的根对象,可以通过Root获取实体中的属性
	//CriteriaQuery对象代表一个顶层查询对象,用来实现自定义查询
	//CriteriaBuilder对象是用来构建查询条件的
	Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,CriteriaBuilder cb);
}

基于Specification机制,我们根据AccountNumber查询订单的实现过程如下

public Account getAccountByAccountNumberBySpecification(String accountNumber) {
	Account account = new Account();
	account.setAccountNumber(accountNumber);
	
	Specification<Account> spec = new Specification<Account>() {
		@Override
		public Predicate toPredicate(Root<Account> root,CriteriaQuery<?> query, CriteriaBuilder cb) {
		  Path<Object> accountNumberPath = root.get("accountNumber");
		  Predicate predicate = cb.equal(accountNumberPath,accountNumber);
		  return predicate;
		}
	};
	return accountRepository.findOne(spec).orElse(new Account());
}