MyBatis从零基础到入门

MyBatis

1 概念

	mybatis 是一个优秀的基于java的持久层(和数据库交互)框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。

	mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句。

	最后mybatis框架执行sql并将结果映射为java对象并返回。采用ORM思想解决了实体和数据库映射的问题,对jdbc 进行了封装,屏蔽了jdbc api 底层访问细节,使我们不用与jdbc api 打交道,就可以完成对数据库的持久化操作。

2 基本使用

  1. 开发步骤

    ①添加MyBatis的坐标

    ②创建user数据表

    ③编写User实体类

    ④编写映射文件UserMapper.xml

    ⑤编写核心文件SqlMapConfig.xml

    ⑥编写测试类

  2. 环境搭建

    2.1 导入MyBatis的坐标和其他相关坐标

<!--mybatis坐标-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.5</version>
</dependency>
<!--mysql驱动坐标-->
<dependency>    
    <groupId>mysql</groupId>   
    <artifactId>mysql-connector-java</artifactId>    
    <version>5.1.6</version>    
    <scope>runtime</scope>
</dependency>
<!--单元测试坐标-->
<dependency>    
    <groupId>junit</groupId>    
    <artifactId>junit</artifactId>    
    <version>4.12</version>    
</dependency>
<!--日志坐标-->
<dependency>    
    <groupId>log4j</groupId>    
    <artifactId>log4j</artifactId>    
    <version>1.2.12</version>
</dependency>

​ 2.2 创建User数据表

​ 2.3 创建User类(属性名需与数据表的字段名相同)

​ 2.4 编写UserMapper映射文件

<?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">    
        <!-- 
    注意: 
        property: 配置为实体类的属性名
        column:   配置为SQL列名
        若对象属性和列名一致时, 可以不配置. 但是为了代码的阅读性,建议都配置上.
    -->
    <resultMap id="userMap" type="com.itheima.domain.User">
        <id property="userId" column="id"></id>
        <result property="userName" column="username"></result>
        <result property="userAddress" column="address"></result>
        <result property="userSex" column="sex"></result>
        <result property="userBirthday" column="birthday"></result>
    </resultMap>	
	<select id="findAll" resultMap="userMap">        
		select * from User    
	</select>
</mapper>

​ 2.5 编写MyBatis核心文件 SqlMapConfig.xml

<?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>    
	<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="root"/>            
			</dataSource>        
		</environment>    
	</environments>    
	
	<mappers> 
		<mapper resource="UserMapper.xml"/> 
	</mappers>
</configuration>

​ 2.6 编写测试代码

//加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//获得sqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new            
                           SqlSessionFactoryBuilder().build(resourceAsStream);	
//获得sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行sql语句
List<User> userList = sqlSession.selectList("userMapper.findAll");
//打印结果
System.out.println(userList);
//释放资源
sqlSession.close();

3 注意事项

  1. #与$的区别
1. #{} 是占位符, 而${}是拼接SQL
	# 方式:
		reparing: select * from user where username like ? 
	$ 方式:
		select * from user where username like '%王%' 
2. ${} 存在SQL注入的风险
3. 当输入参数类型为普通数据类型(包含基本数据类型和string.)
    #{} 变量名可以随意写
    ${} 变量名只能为value
4. 当输入参数为POJO实体类型时.
    #{} 是对象中的属性名
    ${} 也是对象中的属性名

2 MyBatis保存并返回id

<insert id="saveUser" parameterType="com.itheima.domain.User">   
    <!-- 保存成功以后, 获取字段为id的值,赋给User对象的id属性 -->   
    <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">     
    	select last_insert_id();   
    </selectKey>    
    insert into user (username,password) values (#{username},#{password})
</insert>

4 MyBatis核心配置文件

1 层级关系

  • configuration 配置
    • properties 属性
    • settings 设置
    • typeAliases 类型别名
    • typeHandlers 类型处理器
    • objectFactory 对象工厂
    • plugins 插件
    • environments 环境
      • environment 环境变量
        • transactionManager 事务管理器
        • dataSource 数据源
    • databaseIdProvider 数据库厂商标识
    • mappers 映射器

2 environments标签

`其中,事务管理器(transactionManager)类型有两种:

•JDBC:这个配置就是直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。

•MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。

其中,数据源(dataSource)类型有三种:

•UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。

•POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。

•JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

3 mapper标签

该标签的作用是加载映射的,加载方式有如下几种:

•使用相对于类路径的资源引用,例如:

•使用完全限定资源定位符(URL),例如:

•使用映射器接口实现类的完全限定类名,例如:

•将包内的映射器接口实现全部注册为映射器,例如:

4 properties标签:该标签可以加载外部的properties文件

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

5 typeAliases标签:设置类型别名

<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>

<!-- 别名统一配置 -->
<typeAliases>
    <!--
		使用typeAliases配置别名,它只能配置domain中类的别名
		其中: 类名就是别名(不区分大小写)
 	-->
    <package name="com.itheima.domain"></package>
</typeAliases>

5 代理模式开发

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

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

Mybaits底层使用动态代理方式,帮我们实现了接口的方法,因此,我们必须严格按照步骤开发
环境搭建的注意事项:
		第一个:创建UserMapper.xml 和 创建UserMapper.java时名称保持一致。
			在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper
			所以:UserDao 和 UserMapper是一样的
		第二个:在idea中创建目录的时候,它和包是不一样的
			包在创建时:com.itheima.dao它是三级结构  ✔
			目录在创建时:com.itheima.dao是一级目录  ✖
			可以直接创建目录: ✔✔✔
					com/itheima/dao/UserMapper.xml
					
		第三个:mybatis的映射配置文件位置必须和dao接口的包结构相同
		第四个:映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
		第五个:映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名
		第六个: resultType输出类型: 输出类型是接口的方法的返回值类型.
					如果接口中有返回值,必须有输出类型resultType. 
		第七个: parameterType 参数类型:
					如果接口中有查询参数, 必须有参数类型. 

2 映射文件统一配置

<mappers>
    <!--
		<mapper resource="cn/itcast/UserMapper.xml"/>
		<mapper resource="cn/itcast/RoleMapper.xml"/> 每个文件单独配置,很麻烦
 		注意:
		1. package标签配置的是路径或包名
				name="com/itheima/dao"  正确
				name="com.itheima.dao"  正确
		2. 使用package配置方式,映射配置文件名称,必须和接口名称完全一致.否则解析错误
		以下两种方式都可以:
		<package name="com/itheima/dao"></package>
    	<package name="com.itheima.dao"></package>
	-->
    <package name="com.itheima.dao"></package>
</mappers>

3 多参数查询

接口

User login(@Param("username") String name,@Param("password")  String pwd);

映射配置文件

<select id="login" resultType="user" >   
   select * from user where username = #{username}  and password = #{password}
</select>

4 动态Sql

  1. 标签
<select id="findByCondition" resultType="user" parameterType="user">
    select * from user where 1 = 1
    <if test="username !=null and username !=''">
        and username like #{username}
    </if>
     <if test="password !=null  ">
         and password like #{password}
    </if>
</select>
<!--
	注意: <if>标签test属性写的是对象的属性名, 如果是包装类对象要使用OGNL表达式的写法.
	即: userName是对象的属性,严格区分大小写,和数据库字段无关.
-->
  1. 标签
<select id="findByCondition" resultType="user" parameterType="user">        
    SELECT * from user        
    <where>            
        <if test="username !=null">                
            and username like #{username}            
        </if>   
        
        <if test="password !=null">                
            and password like #{password}           
        </if>        
    </where>   
</select>
<!--  
 注意: <if>标签test属性写的是对象的属性名, 如果是包装类对象要使用OGNL表达式的写法.    
 即: userName是对象的属性,严格区分大小写,和数据库字段无关.
-->
  1. 标签
<!--
        直接传递数组查询:
                parameterType="list", 其中list表示List类型.(固定值,可以接受集合和数组)
                collection="array" 其中array: 指定参数类型为数组(固定值)
    -->
<select id="findUserInIds" resultType="user" parameterType="list">
    select * from user
    <where>
        <if test="array!=null and array.length>0">
            <foreach collection="array" open="and id in (" item="id" close=")" separator=",">
                #{id}
            </foreach>
        </if>
    </where>
</select>

foreach标签的属性含义如下:

标签用于遍历集合,它的属性:

•collection:代表要遍历的集合/数组元素,注意编写时不要写#{}

•open:代表语句的开始部分

•close:代表结束部分

•item:代表遍历集合的每个元素,生成的变量名

•sperator:代表分隔符

  1. 标签
<!--更新用户-->
<update id="update" parameterType="user">
    update user
    <set>
        <if test="username!=null">
            username = #{username},
        </if>
        <if test="password!=null">
            password = #{password},
        </if>
    </set>
    where id=#{id}
</update>
<!-- 跟where标签存在意义一样-->

5 SQL片段抽取

Sql中可将重复的片段抽取出来,使用时用include引用即可.

<!--抽取sql片段简化编写-->
<sql id="selectUser">select * from User</sql>

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

6 plugin标签

MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据

开发步骤:

①导入通用PageHelper坐标

<!-- 分页助手 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.2</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>0.9.1</version>
</dependency>

②在mybatis核心配置文件中配置PageHelper插件

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!--选择数据库方言-->
        <property name="helperDialect" value="mysql"/>
        <!--
               reasonable:默认值是false
               true:自动处理首页和尾页,如果是第一页,再点上一页还是第一页不需要在前端去做是否为首页的判断
                     如果是尾页,再点下一页还是最后一页,也不需要在前端做是否为尾页的判断
          -->
        <property name="reasonable" value="true"/>
    </plugin>
</plugins>

③ 修改映射配置文件

<select id="pageQuery"  resultType="user">
    select * from user 
</select>

④测试分页代码实现

@Test
public void testPageHelper(){
    
    
    //设置分页参数
   PageHelper.startPage(1,3);

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

获得分页相关的其他参数

//其他分页的数据
PageInfo<User> pageInfo = new PageInfo<User>(users);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());

6 多表查询

1 一对多查询

<?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.dao.UserMapper">

    <resultMap id="userMap" type="User">
        <!--用户信息-->
        <id property="id" column="id"/>
        <id property="username" column="username"/>
        <id property="password" column="password"/>
        <id property="birthday" column="birthday"/>
        <!--
          订单信息
              property: 对象属性名称
              ofType: 设置集合泛型  List<Order> orders
      -->
        <collection property="orders" ofType="order">
            <id property="id" column="oid"/>
            <id property="ordertime" column="ordertime"/>
            <id property="total" column="total"/>
        </collection>
    </resultMap>

    <select id="findAll" resultMap="userMap">
        SELECT *,o.id oid FROM USER u LEFT JOIN orders o ON u.id=o.uid
    </select>
</mapper>

2 多对多查询

<?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.dao.UserMapper">

    <resultMap id="userMapper" type="user">
        <!--用户信息-->
        <id column="user_id" property="id"></id>
        <result column="username" property="username"></result>
        <result column="password" property="password"></result>
        <result column="birthday" property="birthday"></result>

        <!--角色信息-->
        <collection property="roles" ofType="role">
            <id column="role_id" property="id"></id>
            <result column="rolename" property="rolename"></result>
            <result column="roledesc" property="roledesc"></result>
        </collection>
    </resultMap>

    <select id="findAll" resultMap="userMapper">
        SELECT * FROM USER u,user_role ur,role r WHERE u.id = ur.user_id AND ur.role_id = r.id
    </select>

</mapper>

3 总结 MyBatis多表配置方式:

一对一配置:使用做配置

一对多配置:使用+做配置

多对多配置:使用+做配置

7 注解查询

1 插入后返回id

    /**
     * 保存用户,并返回最后保存用户的id
     * before
     *      1. false : 表示插入成功以后执行
     *      2. true:  表示插入之前执行
     */
    @SelectKey(keyProperty = "id", keyColumn = "id", resultType = Integer.class, before = false, statement = "select last_insert_id()")
    @Insert("insert into  user (username,password,birthday) values(#{username},#{password},#{birthday})")
    public void saveLastUser(User user);

2 一对一查询

 @Select("select * from orders")
    @Results({
    
    
            @Result(id = true, property = "id", column = "id"),
            @Result(property = "ordertime", column = "ordertime"),
            @Result(property = "total", column = "total"),
            @Result(
                    property = "user", //要封装的属性名称  private User user;
                    javaType = User.class, //要封装的实体类型
                    column = "uid", //根据那个字段去查询user表的数据
                    //select属性 代表查询那个接口的方法获得数据
                    one = @One(select = "com.itheima.dao.UserMapper.findById")
            )
    })
    List<Order> findAll();

3 一对多查询

// UserMapper配置
public interface UserMapper {
    
    
    @Results({
    
    
            @Result(property = "id",column = "id",id = true),
            @Result(property = "username",column = "username"),
            @Result(property = "password",column = "password"),
            @Result(property = "birthday", column = "birthday"),
        
            @Result(property = "orders",column = "id",javaType = List.class,
                    many = @Many(select = "com.itheima.dao.OrderMapper.findById")
            )
    })
    @Select("select * from user")
    List<User> findAllUserAndOrder();
}

//OrderMapper配置
public interface OrderMapper {
    
    
   @Select("select * from orders where uid = #{uid}")
    public Order findById();
}

4 多对多查询

// UserMapper配置
public interface UserMapper {
    
    
    @Select("select * from user")
    @Results({
    
    
        @Result(id = true,property = "id",column = "id"),
        @Result(property = "username",column = "username"),
        @Result(property = "password",column = "password"),
        @Result(property = "birthday",column = "birthday"),
        @Result(property = "roleList",column = "id",javaType = List.class,
                many = @Many(select = "com.itheima.mapper.RoleMapper.findByUid"))
})
List<User> findAllUserAndRole();}


// RoleMapper配置
public interface RoleMapper {
    
    
    @Select("select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=#{uid}")
    List<Role> findByUid(int uid);
}

8 一级缓存和二级缓存

	一级缓存基于sqlSession默认开启,在操作数据库时需要构造SqlSession对象,在对象中有一个HashMap用于存储缓存数据。不同的SqlSession之间的缓存数据区域是互相不影响的。一级缓存的作用域是SqlSession范围的,当在同一个sqlSession中执行两次相同的sql语句时,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次查询时会从缓存中获取数据,不再去底层数据库查询,从而提高查询效率。需要注意的是,如果SqlSession执行了DML操作(增删改),并且提交到数据库,MyBatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存中存储的是最新的信息,避免出现脏读现象。当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了。关闭一级缓存后,再次访问,需要再次获取一级缓存,然后才能查找数据,否则会抛出异常。
	
	二级缓存是mapper级别的缓存。使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用HashMap进行数据存储。相比一级缓存SqlSession,二级缓存的范围更大,多个Sqlsession可以共用二级缓存,二级缓存是跨SqlSession的。二级缓存的作用域是mapper的同一个namespace。不同的sqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写到缓存,第二次查询会从缓存中获取数据,不再去底层数据库查询,从而提高效率。

猜你喜欢

转载自blog.csdn.net/lunatic__/article/details/110500769