MyBatis入门
1.Mybtatis简介
1.1 MyBatis的介绍
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。
Mybtais是一个持久层框架,底层是对JDBC的封装,支持普通的sql查询,但是sql语句需要自己写,并且MyBatis不支持自动建表,需要自己先准备好数据库和表
1.2 ORM框架
- ORM:是指对象关系映射,是关系型数据库到实体对象之间的映射
- JPA:本身是一种ORM规范,不是ORM框架,而Hibernate是其中一种实现
- Hibernate:是一个完整的ORM框架,常规CRUD我们不需要写一句SQL,底层封装比较多,所以开发速度快,但是性能方面要比我们写原生sql性能要低的多
- MyBatis:并不是一个完整的ORM框架,因为我们还需要自己去写全部SQL,它的性能要比Hibernate快。
2.Mybatis完成简单的CRUD操作
- 通过Mybatis完成CRUD的操作步骤如下:
1.创建数据库和建表:Mybatis不能自动建表,所以需要我们手动先把数据库和表准备好
2.准备一个普通的项目
3.导入Mybatis需要的jar包: mybatis的核心包、mybatis的依赖包、数据库的驱动包
4.准备domain
5.准备好配置文件:mybatis的核心配置、写sql的xml配置文件
6.测试:需要使用SqlSessionFactory、SqlSession两个核心对象
前面四个步骤很简单,主要将一下mybatis的配置,已经curd的核心代码
2.1 mybatis配置文件
2.1.1 mybatis的核心配置文件:mybatis_config.xml
在Mybatis的文档中可以找到核心配置文件的内容
<?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:配置,是mybatis核心配置文件的根标签-->
<configuration>
<!--配置properties文件,resource属性会告诉核心配置文件直接去项目的resources文件夹下去找-->
<properties resource="jdbc.properties"></properties>
<!--设置别名:typeAliases可以设置多个别名,在这里设置了别名,可以在sql的xml中使用-->
<typeAliases>
<!--每一个typeAlias表示一个别名,type是类的完全限定名,alias是别名,不区分大小写-->
<!--<typeAlias type="cn.lqq.domain.Product" alias="product"/>-->
<!--也可以使用package标签对整个domain包下的类都进行别名设置,别名默认是表名-->
<package name="cn.lqq.domain" />
</typeAliases>
<!--environments:环境们,在这里可以配置多个环境,default属性表示默认的环境,一般默认的都是开发环境-->
<environments default="development">
<!--environment:环境,id是只是一个名称,这里表示默认的开发环境是这个-->
<environment id="development">
<!--transactionManager:事务管理,type类型有两个值:JDBC/MANAGED
JDBC:表示JDBC原生的事务管理进行提交和回滚
MANAGED:表示不做任何事情,就是没有事务
dataSource:表示数据库,连接池,type类型有三个值:UNPOOLED/POOLED/JNDI
UNPOOLED:没有用到连接池,没有请求都会开启和关闭连接
POOLED:底层就是使用的JDBC连接池实现的
JNDI:这个是使用spring框架的时候,使用这个,没有使用到spring框架就使用POOLED
-->
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<!--在这里面配置连接数据库的属性,这里要读取jdbc.properties中的值,所以要配置一下-->
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql:///mybatis" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<!---用来配置mybatis映射器,mapper 对应的是每一个映射文件,就是要读取书写sql的xml文件-->
<mappers>
<!--这个路径表示放在资源文件夹的根目录下,如果放在其他文件夹下,直接写上包名加文件名-->
<mapper resource="Product_Sql.xml"></mapper>
</mappers>
</configuration>
2.1.2 书写sql的xml配置文件:Product_Sql.xml
使用mybatis是需要自己手动写sql的,我们使用一个xml的配置文件用来写sql语句
<?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">
<!--这个配置文件专门用来写sql的,而且是关于product的sql,namespace属性就his一个命名空间
通过namespace属性和sql的id属性,我们可以定位找到具体的sql语句
-->
<mapper namespace="cn.lqq.dao.ProductSql">
<!--查询一条数据的sql:parameterType是在参数的类型,如果有参数就写,没有参数就不写
resultType:指返回值的类型,注意这里是指每一条数据的返回类型,并不是结果集的返回类型
Mybatis的核心配置文件中有设置别名,所以下面有的地方cn.lqq.domain.Product直接用别名product
-->
<select id="findOne" parameterType="long" resultType="product">
select * from product where id=#{id}
</select>
<select id="findAll" resultType="product">
select * from product
</select>
<!--添加数据的sql:如果添加的时候拿到主键的值,需要做以下配置
useGeneratedKeys:表示在添加的时候是否需要拿到主键的值
keyProperty:是指domain类中主键的属性名
keyColumn:是指数据库表中主键的列名称
-->
<insert id="save" parameterType="cn.lqq.domain.Product"
useGeneratedKeys="true" keyProperty="id" keyColumn="id" >
insert into product (productName,dir_id,salePrice,supplier,brand,cutoff,costPrice)
values (#{productName},#{dir_id},#{salePrice},#{supplier},#{brand},#{cutoff},#{costPrice})
</insert>
<!--修改数据的sql-->
<update id="update" parameterType="cn.lqq.domain.Product">
update product set productName=#{productName},dir_id=#{dir_id},salePrice=#{salePrice},supplier=#{supplier},
brand=#{brand},cutoff=#{cutoff},costPrice=#{costPrice} where id=#{id}
</update>
<!--删除一条数据-->
<delete id="delete" parameterType="long">
delete from product where id=#{id}
</delete>
</mapper>
2.2 完成curd的操作
- 查询所有的测试,直接使用junit4单元测试进行测试,步骤是:
1.读取mybatis的核心配置文件
2.创建SqlSessionFactory对象
3.获取到SqlSession对象
4.执行sql语句
@Test
public void testFindOne()throws Exception{
//1.读取mybatis的核心配置文件
Reader reader = Resources.getResourceAsReader("mybatis_configuration.xml");
//2.创建SqlSesssionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
//3.创建SqlSession对象
SqlSession session = factory.openSession();
//4.执行sql
Product product = session.selectOne("cn.lqq.dao.ProductSql.findOne", 1L);
System.out.println(product);
}
2.2.1 抽取工具类
上面的操作步骤对增删改查都适用,只有第四步执行sql的方法可能不一样,所以对于上面三个重复的代码,我们可以抽取一个工具类获取到sqlSession对象,然后就可以通过该对象调用具体方法执行curd
因为SqlSessionFactory对象,是一个应用对应一个factory对象,所以最好设置成单例模式,把他放在静态代码块中赋值,就保证了它是一个单例的对象
public class MyBatiUtils {
private static SqlSessionFactory factory;
static{
try {
//读取核心配置文件
Reader reader = Resources.getResourceAsReader("mybatis_configuration.xml");
factory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession openSession(){
return factory.openSession();
}
}
2.2.2 完善dao层接口
public interface IProductDao {
//查询所以数据
List<Product> findAll();
//查询一条数据
Product findOne(Long id);
//添加数据
void save(Product product);
//修改数据
void updata(Product product);
//删除数据
void delete(Long id);
}
2.2.3 完成dao层接口的实现
需要注意的点是:
1.通过sqlSession执行sql操作的时候,要通过sql的xml配置文件的namespace和具体sql的id来定位到我们要执行的sql语句
2.查询没有事务,但增删改操作必须添加事务才能操作成功
3.执行sql语句的代码最好放在try…catch块中,因为可能定位不到sql语句
public class ProductDaoImpl implements IProductDao {
//因为下面的cn.lqq.dao.ProductSql是命名空间,同一个domain都是一样的,可以提取供公共字段
private static final String NAMESPACE = "cn.lqq.dao.ProductSql.";
@Override
public List<Product> findAll() {
//工具类中已经try..catch了,所以这里不用放在try块里面
SqlSession session = MyBatiUtils .openSession();
try {
//这里之所以要try..catch,是因为下面可能会出现找不到sql语句,
// return session.selectList("cn.lqq.dao.ProductSql.findAll");
return session.selectList(NAMESPACE+"findAll");
} catch (Exception e){
e.printStackTrace();
}
return null;
}
@Override
public Product findOne(Long id) {
SqlSession session = MyBatiUtils .openSession();
try {
return session.selectOne(NAMESPACE+"findOne",id);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void save(Product product) {
SqlSession session = MyBatiUtils .openSession();
try {
session.insert(NAMESPACE+"save",product);
//对于增删改要添加事务,否则提交不了
session.commit();
} catch (Exception e) {
session.rollback();
e.printStackTrace();
}finally {
session.close();
}
}
@Override
public void updata(Product product) {
SqlSession session = MyBatiUtils .openSession();
try {
session.update(NAMESPACE+"update",product);
session.commit();
} catch (Exception e) {
session.rollback();
e.printStackTrace();
}finally {
session.close();
}
}
@Override
public void delete(Long id) {
SqlSession session = MyBatiUtils .openSession();
try {
session.delete(NAMESPACE+"delete",id);
session.commit();
} catch (Exception e) {
session.rollback();
e.printStackTrace();
}finally {
session.close();
}
}
}
2.2.4 在单元测试类中对象dao层进行测试
public class ProductDaoTest {
private IProductDao dao = new ProductDaoImpl();
@Test
public void testFindAll()throws Exception{
dao.findAll().forEach(e-> System.out.println(e));
}
@Test
public void testFindOne()throws Exception{
Product product = dao.findOne(1L);
System.out.println(product);
}
@Test
public void testSave()throws Exception{
Product product = dao.findOne(1L);
product.setId(null);
product.setProductName("雷蛇");
dao.save(product);
System.out.println(product);
}
@Test
public void testUpdate()throws Exception{
Product product = dao.findOne(21L);
product.setProductName("雷蛇2");
dao.updata(product);
}
@Test
public void testDelete()throws Exception{
dao.delete(21L);
}
}
3.Mybatis三大核心对象
3.1 SqlSessionFactoryBuilder
这个对象存在的主要目的就是为了创建SqlSessionFactory对象。这里使用了构造者的设计模式来获取到SqlSessionFactory对象。
一旦你创建了SqlSessionFactory后,这个SqlSessionFactoryBuilder类就不需要存在了。因此SqlSessionFactoryBuilder实例的最佳范围是方法范围(也就是本地方法变量)。
3.2 SqlSessionFactory
一旦被创建,SqlSessionFactory应该在你的应用执行期间都存在。使用SqlSessionFactory的最佳实践是在应用运行期间不要重复创建多次。所以在一个应用中应该只有一个SqlSessionFactory对象,这个对象在你的应用中应该设置为单例模式或静态代理模式
3.3 SqlSession
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能被共享,也是线程不安全的。因此最佳的范围是请求或方法范围。
绝对不能将SqlSession实例的引用放在一个类的静态字段甚至是实例字段中。
这是一个轻量级的对象,通过这个对象可以完成数据库的CRUD操作
4.Mybatis使用的细节
4.1 添加数据时拿到主键
添加数据的时候,主键采用了自动递增的策略,所以添加的时候可以不用输入主键值,数据添加到数据库中以后,数据库会自动生成主键的值,所以在添加数据的时是拿不到主键字段的值的,注意JPA不一样,JPA的EntityManager对象中内置了一级缓存,所以在在添加的时候可以拿到主键的字段值,但是这样会消耗性能。
在Mybatis中添加数据默认是拿不到主键的值的,但如果要拿到刚刚添加的那一条数据的主键的值,可以进行如下配置:
在insert标签中添加下面三个属性useGeneratedKeys,keyProperty,keyColumn
<!--如果添加的时候拿到主键的值,需要做以下配置
useGeneratedKeys:表示在添加的时候是否需要拿到主键的值
keyProperty:是指domain类中主键的属性名
keyColumn:是指数据库表中主键的列名称
-->
<insert id="save" parameterType="cn.lqq.domain.Product"
useGeneratedKeys="true" keyProperty="id" keyColumn="id" >
insert into product (productName,dir_id,salePrice,supplier,brand,cutoff,costPrice)
values (#{productName},#{dir_id},#{salePrice},#{supplier},#{brand},#{cutoff},#{costPrice})
</insert>
4.2 Mybatis中为一个类取别名
Mybatis别名有两种:
1.内置别名,这个可以在Mybatis的文档中查看,基本数据类型和包装类都是Mybatis的内置别名,对于内置的别名,我们可以直接使用。需要注意的一点是,别名不区分大小写,所以所有的基本类型在前面都会加上_,例如:int的别名是_int,long的别名是_long
2.自定义别名:我们在xml文件中写sql的时候,参数和返回值的类型需要写上完全限定名,这里可以直接自定义定义一个别名,自定义别名是在mybatis的核心配置文件中使用typeAliases标签,在这个标签里面可以使用typeAlias子标签定义多个别名。
typeAlias:可以对一个具体的类设置别名。
package :可以对一个包下的所有的类都设置别名,而别名默认就是类名。
<!--设置别名:typeAliases可以设置多个别名,在这里设置了别名,可以在sql的xml中使用-->
<typeAliases>
<!--每一个typeAlias表示一个别名,type是类的完全限定名,alias是别名,不区分大小写-->
<typeAlias type="cn.lqq.domain.Product" alias="product"/>
<!--也可以使用package标签对整个domain包下的类都进行别名设置,别名默认是表名-->
<package name="cn.lqq.domain" />
</typeAliases>
然后在其他使用到cn.lqq.domain.Product的地方直接使用它的别名product
注意:在MyBatis的核心配置文件的根标签configuration下面,所有的标签顺序都是固定的,不能随便写,写错了会报错
4.3 列名与属性名不对应如何解决
当我们数据库表中的列名称和我们实体类的属性名称不匹配的时候,我们在进行CRUD操作的时候,类中的字段到表中的列的自动映射就会失败,这时我们就需要对列名和属性名不一致的进行手动映射。
resultMap标签用来手动设置表中列名和类中字段的映射关系。
id子标签用来设置主键字段的映射
result子标签用来设置其他字段的映射关系
<!--当我们类中的字段名和表中的列名称不一样时,我们可以设置映射,不过映射完在下面要配置-->
<resultMap id="productMap" type="cn.lqq.domain.Product">
<!--如果映射主键字段使用的是id标签,映射的是其他字段使用result标签
property是类的属性名, column是表中的列名称-->
<id property="id" column="id"></id>
<!--对于增改直接在取值的时候修改成dirId就可以了,可以不用引用-->
<result property="dirId" column="dir_id"></result>
</resultMap>
<!--parameterType是在参数的类型,如果有参数就写,没有参数就不写
resultType:指返回值的类型,注意这里是指每一条数据的返回类型,并不是结果集的返回类型 -->
<select id="findOne" parameterType="long" resultMap="productMap">
select * from product where id=#{id}
</select>
4.4 #与$的区别
我们在写sql语句的时候,如果有参数,传值的时候使用的是#{},这里也可以使用${}这个取值,#与 $的区别是:
$:只能获取的是传过来的对象的一个属性(拼接字符串),这种方式有sql注入的问题,能不能使用就不使用。
#:即可以获取传过来的一个值,也可以获取传过来的对象的属性(占位),可以防止sql注入,而且性能更好,如果#不能使用,我们才去使用 $
如下:id是通过#{} 取值的,也可以使用 ${id} 取值,不过不建议使用
<delete id="delete" parameterType="long">
delete from product where id=#{id}
</delete>