MyBatis
JDBC的缺点
注册驱动和SQL语句,后续可能会发生变化,带来繁琐的维护操作。
MyBatis的改进
MyBatis快速入门
1.创建user表
2.创建模块导入坐标
要使用 MyBatis, 只需将 [mybatis-x.x.x.jar]文件置于类路径(classpath)中即可。
如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
再导入mysql测试
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
导入Junit单元测试
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
导入logback日志,并且还需要他的配置文件loback.xml
<!-- 添加slf4j日志api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.20</version>
</dependency>
<!-- 添加logback-classic依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 添加logback-core依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
3.编写MyBatis核心配置文件
用来替换数据库的连接信息
从 XML 中构建 SqlSessionFactory
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
新建mybatis-config.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:///mybatis?useSSL="false"}"/>
<property name="username" value="${root}"/>
<property name="password" value="${root}"/>
</dataSource>
</environment>
</environments>
//加载sql映射文件
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
注:UserMapper.xml和mybatis-config.xml是在resources文件夹下的,是平级的,所以可以直接写路径
4.编写SQL映射文件
UserMapper.xml;操作User表的
OrderMapper.xml;操作Order表的
<?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:名称空间
<mapper namespace="test">
//id是sql语句的唯一标识,resultType是返回结果的类型
<select id="UserSelectAll" resultType="com.itheima.pojo.User">
select * from User;
</select>
<update/>
<delete/>
......
</mapper>
创建对应的类
com.itheima.pojo.User
public class User{
//表里有什么属性,我就写什么属性
private String id;
private String username;
private String passowrd;
private String gender;
private String addr;
......后面再写setter和getter方法以及toString
}
测试类
public static void main(String[] args){
//1.加载mybatis的核心配置文件,获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourcesAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//3.执行sql
//里面传入sql的[名称空间.id(唯一标识)]
List<Object> users = sqlSession.selectList("test.UserSelectAll");
//4.释放资源
sqlSession.close();
}
补充
详细步骤
- Driver那里的jar包,别忘记导入
- 就和我们的mysql一样了,也可以书写sql语句
Mapper代理开发
前面我们这样写,是通过sqlSession中原始的select方法实现的,并且后面的类型也是我们写死的。相当于是一个硬编码,后续维护依然不方便。
Mapper使用原则
-
UserMapper.xml配置,我想用Mapper代理开发,也需要写一个和他同名的Mapper接口:
UserMapper.java -
让接口和配置文件在同一个包下; 在resources中新建一个Directory文件夹:不能使用. 来作为分割,而是使用/ 。保证包名相同即可,即使不在一个目录
- 设置SQL映射文件的namespace属性为Mapper接口全限定名
- 在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
- 修改曾经的mybatis-config.xml路径
-
修改测试类; 获取的是UserMapper接口类型
-
如果Mapper接口名称和SQL映射文件名称相同,并且再同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载
MyBatis核心配置文件
别忘了要遵守标签的前后顺序
environments是环境,配置是开发/测试环境,并且可以用来连接 transactionManager不用管,用来:事物管理的 dataSource:不用管,配置用户名密码就行 (放在environment上面)类型别名typeAlias: 我们通过Mapper.xml配置时,返回类型都需要把包名也上,很麻烦,但是我们可以通过配置别名来改进。
不仅可以不用写包名,而且也不区分大小写了。
<typeAliases>
<package name = "com.itheima.pojo"/>
</typeAliases>
MyBatis实例
- 数据库表tb_brand
- 实体类Brand
-
测试用例 测试用例存储在/test/java/com.itheima.test.test.MyBatisTtest
-
安装MyBatisX插件
帮助我们在映射配置文件中和映射中的方法对应起来,让我们查找对应sql语句的时候,不会那么麻烦。
点击小鸟,就可以帮你跳转,如果你只在映射中书写,映射配置文件没写,就会报如下的波浪线,点一下Alt+Enter,就会帮你在配置文件中自动生成。
补充
编写Mybatis的一般步骤:
1.编写接口方法:Mapper接口 2.编写SQL语句:SQL映射文件(Mapper.xml) 3.执行方法,测试
查询
查询所有数据
查询出来的结果,一定是一个Brand类型的一个集合。
- 编写Mapper接口方法 BrandMapper
public interface BrandMapper{
public List<Brand> selectAll();
}
- 编写SQL映射文件 名字要和接口一样:BrandMapper.xml
//命名空间,返回类型可以直接写路径,不用写详细路径
<mapper namespace = "com.itheima.mapper.BrandMapper">
//编写sql语句resultType = "com.itheima.pojo.Brand"
<select id = "selectAll" resultType = "Brand">
select * from tb_brand;
</select>
</mapper>
- 书写测试用例
test/java/com.itheima.test/MyBatisTest
@Test
public void testSelectAll() thorws IOException{
//1.获取SqlSessionFactory(固定不变)
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//3.获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class)
//4.执行方法,将查询出来的结果保存在brands集合中
List<Brand> brands = brandMapper.selectAll();
System.out.println(brands);
//5.释放资源
sqlSession.close();
}
补充
其实这样写是会存在问题的,因为数据库中,我们的字段是brand_name;而Brand实体类中,我们是randName这种驼峰命名,不能匹配上,就不能帮我们封装数据。
在BrandMapper.xml中配置修改
第一种方法:起别名,对brand_name设置别名为实体类中的名字,as可以省略
<select id = "selectAll" resultType = "brand">
select id,brand_name as brandName...
</select>
第二种方法:sql片段
<sql id = "brand_column'>
将刚刚的起别名放进来
id,brand_name as brandName...
</sql>
后面就可以引用sql片段
<select id = "selectAll" resultType="brand">
select
<include refid = "brand_column"/>
from tb_brand;
</select>
第三种方法:resultMap【最推荐】
1.定义<resultMap>标签
2.在<select>标签中使用resultMap属性替换resultType属性
//先定义一个resultMap标签,和brand类型映射,共有id和result两个标签。
//id是主键字段映射,result是一般字段的映射
<resultMap id = "brandResultMap" type = "brand">
//因为id主键字段是相同的,一般字段不同,所以只写result
//前面是数据库中的字段名,后面的实体类中的名称
<result column="brand_name" property = "brandName"/>
<result column="company_name" property = "companyName"/>
</resultMap>
最后再将 resultType = "brand"改为resultMap="刚刚定义的id名"
<select id = "selectAll" resultMap="brandResultMap">
select * from tb_brand;
</select>
查询详情
根据id,查询出brand类型的集合对象
- 编写Mapper接口方法
Brand selectById(int id);
- 编写SQL映射文件
#{..}和接口方法中的参数保持一致
#{}和$ {}都表示:参数占位符
#{}会将其替换为?防止SQL注入;
${}会直接拼接sql,会存在SQL注入问题;
<select id = "selectById" resultMap="brandResultMap">
select *
from tb_brand where id = #{id};
</select>
- 编写测试方法
@Test
public void testSelectById() thorws IOException{
// 接收参数
int id = 1;
...SqlSessionFactory...
// 执行方法
Brand brand = brandMapper.selectById(id);
System.out.println(brand);
}
- 特殊字符的处理
转移字符: < CDATA区:写一个CD,会提示出来
<select id = "selectById" resultMap="brandResultMap">
select *
from tb_brand where id << #{id};
</select>
条件查询
通过输入的条件,来进行模糊查询
MyBatis如何接收多种参数、有3种方法
- 先写SQL语句
<select id = "selectByCondition" resultMap = "brandResultMap">
select *
from tb_brand
where status = #{status}
and company_name like #{companyName}
and brand_name like #{brandName}
</select>
- 书写映射接口:三种接收方式: 01: 散装参数接收:如果方法中有多个参数,需要使用@Param("SQL参数占位符名称")
List<Brand> selectByCondition(
@Param("status")int status,
@Param("companyName")String companyName,
@Param("brandName")String brandName
);
02:对象参数接收:对象的属性名要和参数占位符名称一致
List<Brand> selectByCondition(Brand brand);
03:map集合参数接收:map键的名称要和参数占位符名称对应上
List<Brand> selectByCondition(Map map);
3.1 散装参数测试用例:散装参数接收:如果方法中有多个参数,需要使用@Param("SQL参数占位符名称")
public void testSelectByCondition() throws IOException{
//1.接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";
//2.处理参数,like#{..}是要有%的,我们在这里添加
companyName = "%" + companyName + "%";
brandName = = "%" + brandName + "%";
.......获取SqlSessionFactory
.......获取SqlSession对象
.......获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//执行方法
List<Brand> brands = brandMapper.selectByCondition(status,companyName,brandName);
System.out.println(brands);
//释放资源
sqlSession.close();
}
3.2 对象参数测试用例:对象的属性名要和参数占位符名称一致
Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);
List<Brand> brands = brandMapper.selectByCondition(brand);
System.out.println(brands);
3.3 map集合参数测试用例:map键的名称要和参数占位符名称对应上
Map map = new HashMap();
map.put("status",status);
map.put("companyNme",companyNme);
map.put("brandName",brandName);
动态条件查询
上面的例子中,如果我用户并没有输入值,那么参数就会对应不上,就会存在bug。
通过动态SQL
动态条件查询
<select id = "selectByCondition" resultMap = "brandResultMap">
select *
from tb_brand
<where>
<if test="status!=null">
and status = #{status}
</if>
<if test = "companyName!=null and companyName!=''">
and company_name like #{companyName}
</if>
<if test = "brandName!=null and brandName!=''">
and brand_name like #{brandName}
</if>
</where>
</select>
单条件的动态查询
//单条件动态查询
List<Brand> selectByConditionSingle(Brand brand)
<select id = "selectByConditionSingle" result = "brandResultMap">
select *
from tb_brand
<where>
<choose>//相当于switch
<when test="status!=null">//相当于case
stateu = #{status}
</when>
<when test="companyName!=null and companyName!=''">//相当于case
companyName = #{companyName}
</when>
<when test="brandName!=null and brandName!=''">//相当于case
brandName = #{brandName}
</when>
</choose>
</where>
</select>
public void testSelectByConditionSingle() throws IOException{
Brand brand = new Brand();
brand.setStatus(status);
...
brandMapper.selectBy
}
添加
如果数据发生变化的,需要进行提交事务!
添加功能
void add(Brand brand);
<insert id = "add">
insert into tb_brand(brand_name,company_name,ordered,description,status)
values(#{brandName},#{companyName},#{ordered},#{description},#{status});
</add>
public void testAdd() throws IOException{
//接收参数
int status = 1
String companyName = "";
String brandName = "";
String description = "";
int ordered = 100;
//封装对象
Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);
brand.setDescription(description);
brand.setOrdered(ordered);
......老三步
//执行方法
brandMapper.add(brand)
//如果数据发生变化的,需要进行提交事务!
sqlSession.commit();
//关闭资源
sqlSession.close();
}
或者在获取SqlSession的时候进行设置自动事务提交
添加之主键返回
添加之后,我还想单独获取到id主键的值;
有可能多表的时候,其中一个表的主键是另一个表的外键,如果获取不到,另一表是会存在bug的
通过useGenerateKeys="true" keyProperty = "接口方法中的主键">
修改
修改全部字段
修改动态字段
提供set标签,防止语法错误
删除
删除一个
根据id删除
批量删除
用数组来存储要删除的id,并且where语句中的数量也是动态变化的
提供了foreach标签来对数组、集合进行遍历;
collection属性:要遍历的数组的名字
item属性:遍历出来的每一个元素
myBatis会将数组参数封装成一个Map集合,Map集合就表示:有key、有value;
默认情况下:key的名称:array;value就是对应的数组
如果不想叫array,那么就需要通过@Params进行设置
separator属性:表示用什么符号进行分割
//void deleteByIds(int[] ids);
void deleteByIds(@Params("ids") int[] ids);
<delete id = "deleteByIds">
delete from tb_brand where id
in(
//<foreach collection = "array">
<foreach collection = "ids" item = "id" separator=",">
#{id}
</foreach>
)
</delete>
open属性:拼接开始的字符串 close属性:拼接结束的字符串
public void testDeleteByIds() where IOException{
//接收参数
int[] ids = {5,7,8};
...
//执行方法
brandMapper.deleteByIds(ids);
}
参数传递
多个参数,要使用@Params【在接口中书写的】:和SQL注入参数保持一致。
注解开发
以前我们将SQL语句写在Mapper.xml中,现在我们写在类中。
简单的语句用注解,复杂的语句(动态SQL的)用xml配置
补充
多表查询
Order.java
private int id;
private Date ordertime;
private double total;
private User user;
这个是OderMapper.xml,这里的id与OrderMapper接口中的所要select的方法名要相同,resultType通过别名设置,可以使用别名书写。
<select id = "findAll" resultType = "order">
select的sql语句
</select>
修改:
resultMap原本是resultType,但是这里需要的类型太多,所以需要通过resultMap来书写
<resultMap id = "orderMap" type = "order">
手动指定字段与实体属性的映射关系
主键需要单独通过id标签设置
<id column = "oid" property = "id"></id>
<result column = "ordertime" property = "ordertime"></result>
<result column = "total" property = "total"></result>
User比较特殊,是user实体的内值
<association property = "user" javaType = "user">
<id column = "uid" property = "(user)id"></id>
<result column = "username" property = "username"></result>
.......
</association>
</resultMap>
<select id = "findAll" resultMap = "orderMap">
select *,o.id oid from orders o,user u where o.uid = u.id
</select>
一对多:一个用户有多个订单,一个订单只从属于一个用户;查询一个用户,与此同时查询出该用户具有的(多个)订单
User.java
public List<Order> orderList;
public List<Order> getOrderList(){}
public void setOrderList(List<Order> orderList){}
<resultMap id = "userMap" type = "user">
<id column = "uid" property = "id"></id>
<result column = "username" property = "username"></result>
...
<collection property = "orderList" ofType = "order">
封装order的数据
<id column = "oid" property = "id"></id>
<result column = "ordertime" property = "ordertime"></result>
</collection>
</resultMap>
测试类
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml")
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.findAll();
for(User user : userList){
}
多对多(三张表,并且需要有一个中间表):查询用户同时查询出该用户的所有角色
Role.java是实体类
User.java是实体类,在User实体类中书写集合存储Role
UserMapper.xml映射
<resultMap id = "userRoleMap" type = "user">
user的信息
<id column = "userId" property = "id"></id>
<result column = "username" property = "username"></result>
...
user内部的rolelist的信息
<collection property = "roleList" ofType = "role"(要取别名)>
<id column = "roleId" property = "id"></id>
<result column = "roleName" property = "roleName"></result>
....
</collection>
</resultMap>
<select id = "findUserAndRoleAll" resultMap = "userRoleMap">
</select>
查询出三张表中,id全部相同的信息
select * from user u,sys_user_role ur,sys_role r where u.id = ur.userId and ur.roleId = r.id
总结
<resultMap id = "orderMap" type = "order">:里面存储type属性中的存储信息,并且还有type属性中引用的其他实体类的信息
如果是一对一:<association property = "user" javaType = "user">...
注解
sqlMapConfig.xml
<mappers>
<package name = "接口所在包"></package>
</mappers>
以前的写法:
<mappers>
<mapper resource = "先加载接口的.xml"></maper>
</mappers>
OrderMapper.java:一对一
@Select("select *,o.id oid from orders o,user u where o.uid = u.id")
@Results([
@Result(column = "oid",property = "id"),
@Result(column = "ordertime",property = "ordertime"),
@Result(column = "uid",property = "user.id"),
@Result(column = "username",property = "user.username")
])
public List<Order> findAll();
一对一之association方式的改进:
@