Mybatis基本工作原理
(1)读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。
(2)加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
(3)构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory。
(4)创建会话对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
(5)Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
(6)MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
(7)输入参数映射。输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。
(8)输出结果映射。输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。
SqlSessionFactoryBuilder、SqlSessionFactory
SqlSessionFactoryBuilder用于构建SqlSessionFactory。这是基于建造者模式而设计的。
SqlSessionFactoryBuilder可以通过InputStream或者Reader获取mybatis的配置文件中的内容自动配置SqlSessionFactory。SqlSessionFactory用来创造SqlSession。而Mybatis的使用就是从SqlSession开始的。
SqlSession
是Sql数据库会话,想要执行Mapper中的方法就需要用到SqlSession。
SqlSession有单例的和非单例的。在与框架整合后,SqlSession是单例的。但没有整合的时候可以是单例的也可以不是。
之所以使用单例SqlSession是因为SqlSession根本没必要有多个。按照SqlSession的生命中期,它在执行完一条sql语句之后就应该结束生命周期。这么做的话并不利于提高开发效率(每执行完一条语句都要写一个session.close()
效率太低。),所以直接用单例,这样它将贯穿整个程序的运行周期而不需要关闭它,大幅度提高开发效率。
Mapper以及Mapper映射文件
Mapper是一个接口。这里面封装所有要执行的方法。在程序运行前,Mybatis会将Mapper映射文件和Mapper接口做关联。举例:
@Mapper//告知系统该类是一个Mapper类,只有拥有该注解的类才能被当做Mapper使用
public interface UserMapper{
List<User> selectAllUsers();
User selectUserById(String id);
}
上面这串代码中的selectAllUsers是获取全部User的方法。该方法有多条结果返回所以返回值是List,而返回的数据是User类型的,所以List的泛型是User。selectUserById是通过ID获取User的方法。因为只有一条记录返回(假设id是User表的主键)所以使用User即可。
当然,只写这些方法是不会有任何作用的。必须还要有对应的Mapper映射文件。
Mapper映射文件和Mapper是一一对应的。Mapper中的方法和Mapper映射文件中的方法也是对应的。
下面是UserMapper配置文件的例子(文件名叫做UserMapper.xml,Mapper映射文件的文件名最好和类名一致):(这里会给出xml文件头部内容,各位读者可以复制头部内容供以后使用。为了方便,后续的章节中所有xml文件的示例代码都不再写头部内容。)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blacol.UserMapper">
<select id="selectAllUsers" resultType="com.blacol.User"><!--必须要有resultType或者resultMap属性-->
<!--id要和方法名一致,resultType的值要和返回值或者泛型一致-->
select * from user
</select>
<select id="selectUserById" resultType="com.blacol.User">
select * from user where id=#{id}
</select>
</mapper>
这样当调用UserMapper下的selectAllUsers的时候Mybatis就会执行UserMapper.xml文件中id为selectAllUsers的sql语句。
下面介绍一下Mapper映射文件的基本内容:
Mapper映射文件是由<mapper>
根标签组成的。在根标签中可以书写<select>
、<insert>
、<update>
、<delete>
标签来对应执行不同的操作。select就是获取,insert就是插入,update就是更新,delete就是删除。
所有标签必须有一个id属性,而select标签必须有resultType或者resultMap属性。(两者选择一个)。insert标签有parameterType属性用于指定参数类型,大多数情况下并不需要该属性。
除了这些还有<resultMap>
等标签也是可以写的。这部分内容等到后面的关联查询部分会详细讲解。
细心的读者可能会发现Mapper方法中带有参数怎么传递给Mapper映射文件呢?在上面的例子已经给出了答案,在selectUserById这个sql语句中有#{id}
。它用来将参数id的值放入到sql语句中。类似的还有${}
,但是这个不常用。两者之间是有区别的:
${}
不会预编译,参数是什么拿过来就直接用了。即参数在sql语句中是这个样子的:... where name=张三
,当sql语句执行时就会报错。同时这个符号不会阻止sql注入(一种攻击方法,它的危害可以在百度上查找)。而#{}
就会预编译,现将参数预编译成?然后将数据自动填入?中,这样参数在sql语句中就变成了... where name=?
,填入参数后变为... where name='张三'
。因此#{}
可以防止sql注入。(类似于java连接数据库时的PreparedStatement
)
实操-商品信息的CRUD(增删改查)
-
创建数据库
我使用DBeaver创建的数据库,使用Navicate也可以。
(我用的sql是8版本的,如果是5版本,字符集是utf8,排序规则是utf8_general_ci)
- 创建数据表
CREATE TABLE `Product` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`price` double(10,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ;
-
创建普通Maven项目
创建项目的过程省略。
-
引入Mybatis依赖并更新maven
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency>
-
创建软件包并创建DBUtil类。包名自拟
public class DBUtil { private static SqlSession session; private static void buildSqlSession() throws IOException { //单例SqlSession SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder(); InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-cfg.xml"); SqlSessionFactory build = builder.build(resourceAsStream); session=build.openSession(); } public static SqlSession getSession() throws IOException { buildSqlSession(); return session; } }
这是单例SqlSession,你也可以创建不是单例的SqlSession。这里以单例的SqlSession为例。
-
创建实体类、Mapper和Mapper映射文件
- 实体类-Product
public class Product { private long id; private String name; private double price; //为了方便,以后Getter和Setter的代码将省略不写同时通过注释标记出该类有Getter和Setter public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } }
- Mapper-ProductMapper
@Mapper public interface ProductMapper { List<Product> selectAllProduct(); Product selectProductById(String id); List<Product> selectProductWhenPriceLessThan(double price); List<Product> selectProductWhenPriceGreaterThan(double price); boolean insertProduct(Product product); boolean updateProduct(String oldId,Product newProduct); int deleteProductById(String id); int deleteAll(); }
- Mapper映射文件-ProductMapper.xml
<mapper namespace="com.blacol.mapper.ProductMapper"> <insert id="insertProduct"> insert into Product values(default,#{name},#{price}) </insert> <update id="updateProduct"> update Product set name=#{arg1.name},price=#{arg1.price} where id=#{arg0} </update> <delete id="deleteProductById"> delete from Product where id=#{id} </delete> <delete id="deleteAll"> delete from Product </delete> <select id="selectAllProduct" resultType="Product"> select * from Product </select> <select id="selectProductById" resultType="com.blacol.entity.Product"> select * from Product where id=#{id} </select> <select id="selectProductWhenPriceLessThan" resultType="com.blacol.entity.Product"> select * from Product where price < #{price} </select> <select id="selectProductWhenPriceGreaterThan" resultType="com.blacol.entity.Product"> select * from Product where price > #{price} </select> </mapper>
- 实体类-Product
-
编写Mybatis配置文件-mybatis-cfg.xml
在src/main/resources文件夹下创建该文件<configuration> <typeAliases> <package name="com.blacol.entity"/> </typeAliases> <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:///mybatisLesson"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <package name="com.blacol.mapper"/> </mappers> </configuration>
-
编写测试类进行测试
在test/java下创建一个测试类MybatisTest。测试类中有一成员session和一个无参构造方法。private SqlSession session=DBUtil.getSession(); public MybatisTest() throws IOException { }
- 测试添加
测试类中添加方法insertTest()
。
运行,此时报错,提示Invalidate Bound,这是因为maven在打包的时候不会把非resources文件夹下的mapper.xml文件打包。可以在pom.xml中添加下列代码解决:@Test//要导入JUnit包不要导入test包 public void insertTest(){ List<Product> products=new ArrayList<>(); ProductMapper mapper = session.getMapper(ProductMapper.class); Product product=new Product(); product.setName("面包"); product.setPrice(3.5); products.add(product); product=new Product(); product.setName("香肠"); product.setPrice(2.2); products.add(product); product=new Product(); product.setName("果酱"); product.setPrice(5.2); products.add(product); for (Product p:products){ if(mapper.insertProduct(p)){ session.commit();//默认的session是手动提交的 System.out.println("插入成功"); }else{ session.rollback(); System.out.println("插入失败"); } } }
<build><!--build标签位于project标签内--> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include><!--让maven打包java文件夹及子文件夹中的所有.xml文件--> </includes> </resource> </resources> </build>
再次运行我们就可以看到有3条插入成功在控制台中。
- 查找测试
- 查找全部
在测试类中添加方法selectAllAndIDTest
@Test public void selectAllAndIDTest(){ ProductMapper mapper = session.getMapper(ProductMapper.class); System.out.println("---获取所有商品---"); List<Product> products = mapper.selectAllProduct(); products.forEach(e-> System.out.println(e.getId()+"-"+e.getName()+"-"+e.getPrice())); System.out.println("---获取id为2的商品---"); Product product = mapper.selectProductById("2"); System.out.println(product.getId()+"-"+product.getName()+"-"+product.getPrice()); }
运行后可以看到结果:
- 按照价格查找商品
在测试类中添加方法selectByPriceTest
@Test public void selectByPriceTest(){ ProductMapper mapper = session.getMapper(ProductMapper.class); System.out.println("获取价格小于3元的商品"); List<Product> products = mapper.selectProductWhenPriceLessThan(3); products.forEach(p-> System.out.println(p.getName()+"-"+p.getPrice())); System.out.println("获取价格大于3元的商品"); List<Product> products2 = mapper.selectProductWhenPriceGreaterThan(3); products2.forEach(p-> System.out.println(p.getName()+"-"+p.getPrice())); }
运行后可以看到相关的商品列出:
- 查找全部
- 更新商品
在测试类中创建测试方法updateTest()
@Test public void updateTest(){ ProductMapper mapper = session.getMapper(ProductMapper.class); System.out.println("修改3号商品:"); Product newProduct=new Product(); newProduct.setName("冬瓜"); newProduct.setPrice(1456.25); boolean b = mapper.updateProduct("3", newProduct); if(b){ session.commit(); System.out.println("更新成功"); }else{ session.rollback(); System.out.println("更新失败"); } }
- 删除商品
创建测试方法deleteTest()
@Test public void deleteTest(){ ProductMapper mapper = session.getMapper(ProductMapper.class); System.out.println("删除1号:"); int i = mapper.deleteProductById("1"); System.out.println("删除了"+i+"条数据"); System.out.println("删除全部货物"); i=mapper.deleteAll(); System.out.println("删除了"+i+"条数据"); }
![step8-4](https://upload-images.jianshu.io/upload_images/6120602-f7c1e19db8dc15fc.png?imageMogr2/a@Mapper
public interface UserMapper {
List<User> selectAllUsers();
List<User> selectUsersByIdards(List<IdCard> idCards);
}uto-orient/strip%7CimageView2/2/w/1240) - 测试添加