[开发框架]-MyBatis

本文深入探讨了MyBatis作为持久层技术的使用,包括JDBC、JdbcTemplate、DBUtils的基础,以及MyBatis的核心思想ORM。详细介绍了MyBatis的配置、映射文件、注解开发、事务管理、设计模式和各种CRUD操作。还讨论了连接池、动态Sql、延迟加载和缓存机制,为开发者提供了全面的MyBatis实践指南。

前言

1.持久层技术及解决方案
JDBC(一种规范, Connection,PreparedStatement,ResultSet)
Spring中的JdbcTemplateApache中的DBUtils(都是工具类)
2. MyBatis蕴含的思想是ORM思想,把数据库表和实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表
3. 依赖

<packaging>jar</packaging>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>

ps:在windows下,Mybatis不区分大小写

入门操作

环境搭建

resources包下创建Mybatis的主配置文件SqlMapConfig.xml,配置环境以及指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件xxxDao.xml,同时要在resources包下创建映射配置文件的目录,然后就是编写映射配置文件(配置文件的解析是一起解析的,有些部分配置可能没用到,但是出现了错误,ide也会报错)

<?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">
<!-- Mybatis的主配置文件-->
<configuration>
    <!-- 配置环境-->
    <environments default="mysql">
        <!-- 配置mysql的环境-->
        <environment id="mysql">
            <!--配置事务的类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的四个基本信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/..."/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <mappers>
        <mapper resource="com/xxx/www/dao/UserDao.xml"></mapper>
    </mappers>
</configuration>
<?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">
<!--要加上namespace标签,其值必须为dao的全限定类名-->
<mapper namespace="com.xxx.www.dao.UserDao">
    <!--配置查找所有用户的方法   id的值必须为方法的名称   
整个select标签的值为要执行的sql语句,其中结尾的分号可写可不写
resultType的值为sql语句查询后应该封装的对象-->
    <select id="findAll" resultType="com.xxx.www.po.User">
        select * from user
    </select>
</mapper>

如果返回的参数是集合类型,那么 resultType 属性的值应该是集合中的泛型类型而不是集合类型 (java.util.List 这种)

ps:
a.在mybatis中把持久层的操作接口名称和映射文件也叫做Mapper,也就是说 UserDao 和 UserMapper是一样的
b.在idea中创建目录和创建包是不一样的,创建包时直接创建com.xxx.www是三级结构三级包,但(在resources包下)创建目录时直接输入com.xxx.www得到的是一级结构一级目录,所以在resources包下创建映射配置文件时目录应该一级一级创而不能像创建包一样
c.映射配置文件的结构位置必须与dao接口的结构,位置相同

基础步骤

public static void main(String[] args) throws Exception{
        //1读取配置文件
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2创建SqlSessionFactory(工厂模式)
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        //3创建SqlSession,这个对象才是真正操作数据库的对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4创建dao的代理对象(代理模式)
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        //5使用代理对象执行方法
        List<User> users = userDao.findAll();
        //6释放资源
        sqlSession.close();
        inputStream.close();
    }

使用注解

移除UserDao.xml文件,在接口的findAll()方法加上@Select注解

public interface UserDao {
    /**
     *查找所有用户
     * @param
     * @return java.util.List<com.xxx.www.po.User>
     */
    @Select("select * from user")
    List<User> findAll();
}

在SqlMapConfig.xml中修改mappers标签,用class属性指定被注解的dao全限定类名:

<mappers>
    <mapper class="com.xxx.www.dao.UserDao"></mapper>
</mappers>

mybatis中的设计模式

SqlSessionFactory的创建细节全部忽略,将其封装在SqlSessionFactoryBuilder中,只用build()方法进行创建-
构建者模式
SqlSession的创建不依赖于其它的类,而是用SqlSessionFactory来创建
工厂模式
dao接口的实现类对象是用 动态代理 生成的

增删查改

插入

在dao接口中定义方法

/**
 *保存用户
 * @param user
 * @return void
 */
void saveUser(User user);

在dao接口对应的xxxdao.xml中加入对应插入方法的标签

<!--保存用户,parameterType属性指定实体类
       在取实体类的属性时,我们的getter和setter方法是idea快捷生成的,所以实体类的成员变量名称就是属性名称,
       如果getter和setter是我们自己手写出来的,那属性名应该以get和set后面的后缀为准-->
<insert id="saveUser" parameterType="com.xxx.www.po.User">
   	<!-----下面是配置插入操作后,获取被插入数据的id:
   	KeyProperty代表要返回的值的名称(实体类中的属性名),order为AFTER指的是在插入后再进行操作。如此操作后,原来被调用 insert 插入的实体类对象的id成员变量就会被赋值为插入数据库时的主键id的值-->
   	<selectKey KeyProperty="id" KeyColumn="id" resultType="int" order="AFTER">
   		select last_insert_id();
   	</selectKey>
    insert into user(username,address,sex,birthday)values (#{username},#{address},#{sex},#{birthday})
</insert>

其中,对于插入后获取插入时的主键值的设置,也可以通过属性 useGeneratedKeys=“true” 来实现,同时需要用 keyProperty 属性指定获取的这个主键值要赋值到实体类中的哪个成员变量上 (即实体类中对应数据库主键的成员变量)
如果是使用 Mybatis-Plus,他的 insert 方法默认就具有这种效果,无需再配置

执行,要记得手动提交事务(注意到控制台打印日志,"Setting autocommit to false",表示关闭了自动提交事务,所以要手动提交)
也可以设置事务的自动提交,在SqlSessionFactory的openSession()方法中参数传入布尔值就可以设置事务的自动提交或手动提交(true即为自动提交)

User user = new User();
user.setUsername("name");
user.setAddress("address");
user.setSex('男');
user.setBirthday(new Date());
userDao.saveUser(user);
//提交事务
sqlSession.commit();

修改

/**
  * 更新操作
  * @param user
  * @return void
  */
void updateUser(User user);
<!--更新用户-->
 <update id="updateUser" parameterType="com.xxx.www.po.User">
     update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}
 </update>
 <!--使用set标签
     <update id="updateUser" parameterType="com.xxx.www.po.User">
         update user 
         <set>
         	<if test="username != null">
         		username=#{username},
         	</if>
         	<if test="address!= null">
         		address=#{address},
         	</if>
         	<if test="sex!= null">
         		sex=#{sex},
         	</if>
         	<if test="birthday!= null">
         		birthday=#{birthday},
         	</if>
         </set>
         where id=#{id}
 </update>
 -->

同样要记得提交事务的操作

删除

/**
  *删除用户
  * @param userId
  * @return void
  */
 void deleteUser(Integer userId);
<!--删除用户 ,此处的parameterType可以写 int Integer INTEGER INT java.lang.Integer
 这里的参数由于是基本数据类型或基本类型包装类,且只有一个,所以取参数时(uid)叫什么名字都可以,只是起到占位符的作用-->
 <delete id="deleteUser" parameterType="java.lang.Integer">
     delete from user where id = #{uid}
 </delete>

查询一个的操作

/**
  *根据用户id查询用户信息
  * @param userId
  * @return com.xxx.www.po.User
  */
 User findById(Integer userId);
<select id="findById" parameterType="INT" resultType="com.xxx.www.po.User">
     select * from user where id = #{uid}
</select>

模糊查询

/**
  *根据名称模糊查询用户信息
  * @param username
  * @return java.util.List<com.xxx.www.po.User>
  */
List<User> findByName(String username);
<!--模糊查询的占位符’%‘要在调用方法给出参数时给出(如参数为"%王%")-->
<select id="findByName" resultType="com.xxx.www.po.User" parameterType="string">
	<!--这种方式是用PreparedStatement的参数占位符来生成sql语句,这种方式在开发中更为常用  -->
    select * from user where username like #{name}
    <!--另一种配置方式,提供了占位符。是用String中的字符串拼接来生成sql语句
    select * from user where username like '%${value}%'  -->
</select>

使用$可以把参数原封不动地填到SQL中,在某些场景我们就是要使用参数原来的不加逗号的样子,就需要使用$的方式,但是这种方式又可能会有SQL注入的问题,此时我们可以使用sql标签

聚合函数

/**
  *查询总用户数
  * @param
  * @return int
  */
int findTotal();
<select id="findTotal" resultType="int">
    select count(id) from user 
</select>

其它知识点

参数深入

  1. 传入pojo对象:Mybatis通过OGNL表达式(Object Graphic Navigation Language)解析对象字段的值
    类中的写法:user.getUserName()
    OGNL表达式:user.username
  2. 传入pojo包装对象
    有时候查询条件是综合的,不仅包括用户查询条件还包括其它查询条件(比如把用户购买商品信息也作为查询条件),这时可以定义一个QueryVo实体类,使其中包含其它的实体类(user等其它实体类作为一个Vo的参数)
  3. 解决实体类属性名和数据库列名不对应的方法
    a.起别名select 数据库对应列名 as 实体类属性名,... from user
    b.在映射配置文件中加上配置信息
<!--id是这个resultMap的唯一标识,type指的是查询对应的实体类-->
<resultMap id="userMap" type="com.xxx.www.po.user">
    <!--主键字段的对应,property是属性名,column是数据库对应列名-->
    <id property="userId" column="id"></id>
    <!--其它字段,这里的property对应的是java中的属性,所以是严格区分大小写的-->
    <result property="userName" column="username"></result>
    <result property="userAddress" column="address"></result>
</resultMap>

还可以用 jdbcType 属性指定某一列在 MySQL 中的数据类型是什么
然后要记得在查询的select标签中把resultType属性去除,改为resultMap:

<select id="findAll" resultMap="userMap">
    <!--select id as userId,username as userName,address as userAddress from user;-->
    select * from user
</select>

ps:第一种方法直接修改sql语句,第二种方法在运行时要多解析一个resultMap的xml,所以底层运行效率是第一种更快;
但第一种方法的话就必须把每一个查询语句都去修改,而第二种方法只需把相关的select标签中的属性改为resultMap即可不用修改sql语句,开发效率更快

当然,实际开发中最好保持数据库列名与实体类属性名一致

SqlMapConfig.xml中的其它标签

properties标签

可以在properties标签内部配置信息如数据库连接信息,也可以通过属性引用外部配置文件信息:
resources属性:用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下:

<properties resource="jdbcConfig.properties"></properties>

url属性:是按照url的写法来写文件地址,Uniform Resource Locator,统一资源定位符,唯一标识一个资源的位置,写法:协议 主机 端口 URI(如http://localhost:8080/mybatisserver/demoServlrt。URI:Uniform Resource Identifier,统一资源标识符,它是在应用中可以唯一定位一个资源的。计算机中文件存储使用的是file协议

typeAliases标签

<typeAliases>
    <!--typeAlias用于配置别名,type属性指定的是实体类全限定类名,alias属性指定别名,当指定了别名就不再区分大小写,如本例中,如果其它地方要用到实体类User,可以写User,usER,uSeR...-->
    <typeAlias type="com.xxx.www.po.User" alias="user"></typeAlias>
</typeAliases>
<typeAliases>
	<!--用于指定要配置别名的包,当指定后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写,这样可以不用每次都去写全限定类名-->
    <package name="com.xxx.www.po"/>
</typeAliases>

在指定映射配置文件的标签mappers中也有package标签的使用:

<mappers>
    <!--package标签是用于指定dao接口所在的包,当指定了之后就不需要写mapper以及resource或者class了-->
    <package name="com.xxx.www.dao"></package>
</mappers>

连接池

mybatis连接池提供了三种方式的配置,配置的位置在主配置文件中的dataSource标签,type属性就是表示采用何种连接池方式,取值为:
POOLED: 采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对该规范的实现(空闲池和活动池)
UNPOOLED:采用传统的获取连接的方式,虽然也实现javax.sql.DataSource接口,但是没有使用池的思想,即没有容器的概念,每次用都重新获取连接
JNDI:采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到的DataSource是不一样的(注意:如果不是web或者maven的war工程,是不能使用的;tomcat服务器采用的是dbcp连接池

动态Sql语句

if标签

<!--根据条件查询-->
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
    select * from user where 1=1
    <!--如果在判断时要用“且”的判断,要用and而不能使用 &&-->
    <if test="username != null">
        and username = #{username}
    </if>
    <if test="usersex != null">
        and usersex = #{usersex}
    </if>
</select>

where标签

将if标签放入where标签可以不用加上where 1=1

<select id="findUserByCondition" resultMap="userMap" parameterType="user">
    select * from user
    <where>
    	<if test="username != null">
        	and username = #{username}
	    </if>
	    <if test="usersex != null">
	        and usersex = #{usersex}
	    </if>
	</where> 
</select>

foreach标签

<!--根据queryVo中的id集合查询用户列表-->
<select id="findUserInIds" resultMap="userMap" parameterType="queryvo">
   select * from user
   <where>
       <if test="ids != null and ids.size()>0">
           <foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
               #{uid}
           </foreach>
       </if>
   </where>
</select>

注意!! mybatis在遍历集合之前,需要判断集合是否为null以及是否为空,否则会报错无法执行。而且看根据上面这个例子的话。如果集合确实为空,会执行 select * from user,即把所有记录都查出来,所以最好是在调用类似的dao方法前在service中先对要作为查询参数的集合进行判空操作,为空的话就不用调用dao方法了,也不用担心dao方法执行时出现无预计的结果
标签中 collection 属性填的是集合类型参数的名称,可以在代码中使用 @Param 注解指定:

List<CheckPoint> findByName(@Param("nameList") List<String> name);

或者不使用注解,mapper 方法的参数列表在 xml 解析时会带按顺序带有参数名 param1,param2,…,在 xml 中使用 param1,param2,…,即可取到列表中的参数

sql标签

sql标签用于抽取重复的sql语句:

<sql id="defaultUser">
	<!--由于后面可能还要拼接其它内容,所以这里不要再加分号-->
    select * from user
</sql>
 <select id="findAll" resultMap="userMap">
    <include refid="defaultUser"></include>
</select>

choose标签

choose标签可以实现类似于 switch-case 的单条件多分支语句

表间联查

一对多(多对一),基于外键

account为从表,user`为主表,在查询account表时返回其所属user的全部信息:

public class Account implements Serializable {
   private Integer id;
   private Integer uid;
   private Double money;

   //从表实体类应该包含一个主表实体的对象引用
   private User user;
} 

在AccountDao.xml中:

<resultMap id="accountUserMap" type="account">
    <id property="id" column="aid"></id>
    <result property="uid" column="uid"></result>
    <result property="money" column="money"></result>
    <!--这里的column指的应该是外键,要加javaTYpe属性表示封装到哪个实体类-->
    <association property="user" column="uid" javaType="com.xxx.www.po.User">
        <id property="id" column="id"></id>
        <result column="username" property="username"></result>
        <result column="address" property="address"></result>
        <result column="sex" property="sex"></result>
        <result column="birthday" property="birthday"></result>
    </association>
</resultMap>

<select id="findAll" resultMap="accountUserMap">
	<!--as用于在查询时起别名,这个别名只会影响当前这个查询不影响后续的查询-->
    select u.*,a.id as aid,a.uid,a.money from account a,user u where u.id=a.uid
</select>

查询user时同时查询其所有account:

public class User implements Serializable {
    private Integer id;
    private String username;
    private String address;
    private String sex;
    private Date birthday;
    
    //一对多关系映射,主表实体应该包含从表实体的集合引用
    private List<Account> accounts;
}

在UserDao.xml中:

<resultMap id="userAccountMap" type="user">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result column="address" property="address"></result>
    <result column="sex" property="sex"></result>
    <result column="birthday" property="birthday"></result>
    <!--配置user对象中accounts集合的映射-->
    <collection property="accounts" ofType="com.xxx.www.po.Account">
        <id column="id" property="id"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
    </collection>
</resultMap>
<select id="findById" resultMap="userAccountMap">
    <!--左外连接,使左边表的所有信息都会保存-->
    select * from user u left outer join account a on u.id = a.uid;
</select>

多对多,基于第三张表

用户与角色
获取角色下所属用户信息:

public class Role implements Serializable {
    private Integer roleId;
    private String roleName;
    private String roleDesc;
    //多对多的关系映射,一个角色可以赋予多个用户
    private List<User> users;
}
<resultMap id="roleMap" type="role">
    <id property="roleId" column="rid"></id>
    <result property="roleName" column="role_name"></result>
    <result property="roleDesc" column="role_desc"></result>
    <collection property="users" ofType="user">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result column="address" property="address"></result>
        <result column="sex" property="sex"></result>
        <result column="birthday" property="birthday"></result>
    </collection>
</resultMap>
<select id="findAll" resultMap="roleMap">
<!--较长的sql语句要换行记得(行尾或行首)加空格-->
    select u.*,r.id as rid,r.role_name,r.role_desc from role r 
      left outer join user_role ur on r.id = ur.rid 
      left outer join user u on u.id = ur.uid
</select>

查询用户获取用户所包含的角色信息:
与a同理,注意sql语句应改为:

select u.*,r.id as rid,r.role_name,r.role_desc from user u 
  left outer join user_role ur on u.id = ur.uid 
  left outer join role r on r.id = ur.rid

mybatis中的延迟加载

引入

在查询用户时,每个用户都有很多账户,要不要把关联的账户一起查出来?如果要一起查出来,查询的结果会占据很大的内存空间,所以在查询用户时,其账户信息应该是什么时候使用再什么时候查出来
在查询账户时,所属用户的信息也是需要的,所以所属用户信息应该是随着账户信息查询时一起查出来

延迟加载

在真正使用数据时才发起查询,不用的时候不查询,即按需查询(懒查询)

立即加载

不管用不用,只要一调用方法,马上发起查询

在对应的四种表关系中,对于一对多与多对多,通常情况下我们都是采用延迟加载;对于多对一与一对一,通常情况下我们都是采用立即加载

实现查询账户信息时,延迟加载所属用户信息(一对一):

<resultMap id="accountUserMap" type="account">
    <id property="id" column="id"></id>
    <result property="uid" column="uid"></result>
    <result property="money" column="money"></result>
    <!--select属性指定的内容是查询用户的唯一标识
        column属性指定的内容是用户根据id查询时所需要的参数的值-->
    <association property="user" column="uid" javaType="com.xxx.www.po.User" select="com.xxx.www.dao.Userdao.findById"></association>
</resultMap>
<select id="findAll" resultMap="accountUserMap">
    select * from account
</select>


<select id="findById" parameterType="INT" resultType="com.xxx.www.po.User">
    select * from user where id = #{uid}
</select>

要记得在SqlMapConfig.xml中添加settings标签

<settings>
    <!--开启mybatis支持延迟加载-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

实现查询用户时延迟加载账户信息(一对多):

<resultMap id="userAccountMap" type="user">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result column="address" property="address"></result>
    <result column="sex" property="sex"></result>
    <result column="birthday" property="birthday"></result>
    <collection property="accounts" ofType="com.xxx.www.po.Account" select="com.xxx.www.dao.AccountDao.findAccountByUid" column="id"></collection>
</resultMap>
<select id="findAll" resultMap="userAccountMap">
    select * from user
</select> 

<select id="findAccountByUid" resultType="account">
    select * from account where uid = #{uid}
</select>

mybatis中的缓存

基本概念

缓存是指存在于内存中的临时数据,使用缓存可以减少和数据库的交互次数,提高执行效率经常查询并且不经常改变的数据,以及数据的正确与否对最终结果影响不大的,适用于缓存;而经常改变的数据以及数据的正确与否对最终结果影响很大的不适用于缓存,例如商品的库存,银行的汇率,股市的牌价等等

一级缓存

指的是Mybatis中SqlSession对象的缓存。当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供的一块区域中,该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去SqlSession中查询是否有,有的话就直接拿出来用。当SqlSession对象消失时,一级缓存也就消失了。因此,可以通过调用close()方法关闭SqlSession来清空缓存 ( 当调用SqlSession的修改添加删除commit(),close()等方法时,就会清空一级缓存 ),也可以调用clearCache()方法直接清空缓存

二级缓存

指的是mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。存放的内容是数据,不是对象

使用步骤

  1. 让mybatis支持二级缓存(在SqlMapConfig.xml中配置)
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
  1. 让当前的映射配置文件支持二级缓存(在xxxDao.xml中配置)
<cache/>
  1. 让当前的操作支持二级缓存(在select标签中配置)
<select id="findById" parameterType="INT" resultType="com.xxx.www.po.User" useCache="true"> 
    select * from user where id = #{uid}
</select>

注解开发

数据库中列与实体类属性的对应

@Select("")
@Results(id="userMap",value={
	@Result(id=true,column="id",property="userId"),
	@Result(column="user_name",property="userName")
})
User selectMethod();

使用@Results注解,注解中的value属性用于指定数据库中列与实体类属性的对应关系,这些对应关系用@Result注解指定,注解中的id属性是布尔型,区别该对应关系是否是id的,column属性指定数据库中的列名,property属性指定实体类中的属性名。@Results注解还有一个id属性,指定后可以在其它方法引用这个对应关系

@Select("")
@ResultMap(value={"userMap"})
User selectMethod2();

@ResultMap注解的value属性用于指定使用哪个@Results,可以指定多个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值