一、mybatis是什么
- MyBatis是一个ORM的数据库持久化框架
框架(Framework)是一个框子——指其约束性,也是一个架子——指其支撑性,框架本身不解决问题。 - 什么叫数据库持久化:
数据库持久化是数据持久化的其中一种,就是把内存中的数据保存到数据库中。 - Java中最简单的就是使用jdbc来完成数据库持久化:但是代码重复,拼接复杂。。
- ORM框架映射方式?
1、Sql操作方式(对jdbc进行封装):
把SQL配置到配置文件中,通过不同SQL中完成对象实体和数据库关系相互转换的操作。(mybatis的实现方式)
2、完整操作:
直接映射的是对象实体和数据库关系映射。操作数据库关系,不用写SQL有框架自己生成。(JPA、Hibenate实现方式)
mybatis的取别在于是通过映射sql(不完全映射)来操作数据库
二、准备jar包,实体类
- 新建普通Java项目
- 数据库准备一张表product
创建实体类
public class Product {
private Long id;
//商品名称
private String productName;
//品牌
private String brand;
//供应商
private String supplier;
//零售价
private BigDecimal salePrice;
//进价
private BigDecimal costPrice;
//折扣价
private Double cutoff;
//商品分类编号
private Long dir_id;
//提供getter与setter...
}
- 准备 dao层
public interface IProductDao {
void save(Product product);
void remove(Long id);
void update(Product product);
Product loadOne(Long id);
List<Product> loadAll();
}
三、搭建环境
- 新建lib文件夹
- recourses文件夹
- 导入相应的jar包
- recourses中新建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>
<!--加载数据库资源文件-->
<properties resource="db.properties"></properties>
<!--给类取别名 在映射关系中可以直接使用别名表示-->
<typeAliases>
<!-- 细节,注意路径是斜杠,实体是点-->
<typeAlias type="com.lr.mybatis._03_using_detail.domain.Product" alias="Product" />
<!-- 包的配置:项目,添加了包之后,类名就是别名!!!! -->
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</dataSource>
</environment>
</environments>
<!--加载实体映射的xml文件-->
<!--通过一个个数据库与实体的关系映射中的sql语句来操作数据库-->
<mappers>
<!--切换关系映射的时候一定要注释掉之前的!-->
<!--<mapper resource="com/lr/mybatis/_01_hello/domain/Productmapper.xml"/>-->
<!--抽取工具的版本-->
<!--<mapper resource="com/lr/mybatis/_02_mybatis_util/domain/Productmapper.xml"/>-->
<!--<mapper resource="com/lr/mybatis/_03_using_detail/domain/Productmapper.xml"/>-->
<mapper resource="com/lr/mybatis/_04_batch_many/domain/Productmapper.xml"/>
</mappers>
</configuration>
- recourses中新建db.properties
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql:///mybatistest
db.username=xxx
db.password=xxxxx
- 在实体类中新建xx类Mapper.xml
<?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:命名空间
1 相当于java包,配置sqlid进行唯一区分sql:namespace+sqlId
2 规范:domain全限定名+Mapper:拷贝以免出错
-->
<!--实现方法的时候,session传入的是命名空间加上,命名空间的方法名字 区分其他关系映射-->
<mapper namespace="com.lr.mybatis._02_mybatis_util.domain.ProductMapper">
<!--
sql语句
id:配合namesapce唯一确定sql,名称和方法名一致
parameterType:参数类型
resultType:返回值类型
-->
<select id="loadOne" parameterType="long" resultType="Product">
select * from product where id = #{id}
</select>
<!--List<Product> loadAll();
resultType:如果返回值是集合,那返回类型不是集合,是集合里面内容类型
-->
<select id="loadAll" resultType="com.lr.mybatis._02_mybatis_util.domain.Product">
SELECT * FROM product
</select>
<!--void save(Product product);-->
<!--select和insert不影响,主要是看里面的语句-->
<insert id="save" parameterType="com.lr.mybatis._02_mybatis_util.domain.Product" >
INSERT INTO product (productName,brand,supplier,salePrice,costPrice,cutoff,dir_id) VALUES (
#{productName},
#{brand},
#{supplier},
#{salePrice},
#{costPrice},
#{cutoff},
#{dir_id})
</insert>
<!--void remove(Long id);-->
<delete id="remove" parameterType="long" >
DELETE FROM product WHERE id = #{id}
</delete>
<!--void update(Product product);-->
<update id="update" parameterType="com.lr.mybatis._02_mybatis_util.domain.Product" >
update product set
productName = #{productName},
brand = #{brand},
supplier = #{supplier},
salePrice = #{salePrice},
costPrice = #{costPrice},
cutoff = #{cutoff},
dir_id = #{dir_id}
where id = #{id}
</update>
</mapper>
- 在MyBatis-Config.xml中引入关系映射xml
<mappers> <!--切换关系映射的时候一定要注释掉之前的!--> <!--<mapper resource="com/lr/mybatis/_02_mybatis_util/domain/Productmapper.xml"/>--> </mappers>
小结:
配置MyBatis-Config.xml
引入关系映射xml,关系映射文件中的sql决定了功能
映射文件要取命名空间,方便区分使用
映射文件中的sql方法取id,实现的时候调用这个id就可以
注意 sql中的配置
注意配置文件中的文件路径和实体路径,如果是文件的地址,是斜杠划分
实体类是.划分
实现方法的时候,是使用的命名空间的命名加上方法id
MyBatis-Config.xml引入的是xml映射关系文件
四、impl层实现
- 读取配置文件
- SqlSessionFactory获取
- 获取SqlSession对象
问题:
每次实现方法都需要创建,获取,关闭。。
@Override
public void save(Product product) {
SqlSession sqlSession = null;
try {
//加载mybatis.xml文件
Reader reader = Resources.getResourceAsReader("MyBatis-Config.xml");
//获取session工厂对象
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
//测试工厂对象是否加载成功
System.out.println(sessionFactory);
//拿到session
sqlSession = sessionFactory.openSession();
//返回集合selectList
sqlSession.insert("com.lr.mybatis._01_hello.domain.ProductMapper.save",product);
sqlSession.commit();
}catch (Exception e){
sqlSession.rollback();
e.printStackTrace();
}finally {
//关闭session
if (sqlSession!=null){
sqlSession.close();
}
}
}
入门小结
- 准备配置文件-Mybatis-Config.xml
1)环境配置-配置怎么访问数据
2)导入映射配置-先不配置 - 基于配置文件创建SqlSessionFactory
1)通过配置文件获取Reader
2)SqlSessionFactoryBuilder.build(reader) - 通过SqlSessionFactory获取SqlSession,并调用对应的api完成对应操作
1)配置映射文件并且在Mybatis-Config.xml导入
2)获取SqlSession
3)调用api-selectOne,selectList,insert,delete,update
4)如果是增删改要做事务控制 commit rollback
5)关闭sqlsession
五、工具类抽取
为了减少重复代码,抽取一个工具类,可以方便的获取session对象和关闭对象
public enum MybatisUtil {
INSTANCE;
//获取session的工厂
private static SqlSessionFactory sessionFactory;
//静态代码块,运行就创建
//在其他地方可以随时调用获取session
static {
//加载工厂对象
//加载mybatis.xml文件
try {
//注意try catch中如果先没有处理异常,会报错
Reader reader = Resources.getResourceAsReader("MyBatis-Config.xml");
//获取session工厂对象
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
}catch (Exception e){
e.printStackTrace();
}
}
//得到sqlsession的方法
public SqlSession openSqlSession (){
//System.out.println("================");
SqlSession sqlSession = sessionFactory.openSession();
//System.out.println("+++++++++++++++++++++++");
return sqlSession;
}
//关闭 SqlSession 的方法 需要传入sqlSession 确定要关闭的对象是哪一个
public void endSqlSession(SqlSession sqlSession){
if (sqlSession!=null){
sqlSession.close();
}
}
}
- 注意枚举的单例模式
- 静态代码块
- 异常处理
- 将方法中的重复代码全部替换掉
六、mybatis使用一些细节
- 保存时返回id
- 如果没有在关系映射xml中配置时如果马上打印使用这个被添加的对象时,id是空的
- Product{id=null, productName=‘测试’, brand=‘null’, supplier=‘null’, salePrice=null, costPrice=null, cutoff=null, dir_id=null}只添加了一个名字,但是id是空的
- mybatis不像jpa,没有自动维护关联表关系的功能所以需要得到返回的id,自动将id设置到刚才添加的对象中,并且我们能使用设置到中间表,手动维护关系
- 解决方法:
在映射关系xml中的sql方法配置时,加上这些设置
<!--添加时 useGeneratedKeys是否返回id
keyProperty 返回的数据的类型
keyColumn 返回的是什么
-->
<insert id="save" parameterType="com.lr.mybatis._03_using_detail.domain.Product"
keyColumn="id"
keyProperty="id"
useGeneratedKeys="true"
>
- 别名
在MyBatis.xml中配置别名,实体关系映射xml中就可以直接使用别名
比如
<select id="loadAll" resultType="Product">
SELECT * FROM product
</select>
配置的方法
<configuration>
<!--加载数据库资源文件-->
<properties resource="db.properties"></properties>
<!--给类取别名 在映射关系中可以直接使用别名表示-->
<typeAliases>
<!-- 细节,注意路径是斜杠,实体是点-->
<typeAlias type="com.lr.mybatis._03_using_detail.domain.Product" alias="Product" />
<!-- 包的配置:项目,添加了包之后,类名就是别名!!!! -->
</typeAliases>
</configuration>
- 日志支持
log4j-1.2.17.jar
slf4j-api-1.7.2.jar
slf4j-log4j12-1.7.2.jar
在资源文件中配置log4j.properties
log4j.rootLogger=DEBUG, stdout
#log4j.rootLogger=NONE
log4j.logger.com.lr=TRACE
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
一定要根据自己的包名情况进行修改
如果配置成功,那么在MyBatis运行操作数据时就可以看到相应的日志了。
如果要打印到debug,需要改成
log4j.rootLogger=DEBUG, stdout
- #与$区别
- #{OGNL表达式}
MyBatis会把这个表达式使用?(占位符)替换,作为一个sql参数使用:推荐使用
比如name的值为:
定义SQL: select * from t_user where name = #{name}
最终SQL: select * from t_user where name = ?
- ${OGNL表达式} 的区别
MyBatis会把这个表达式的值替换到sql中,作为sql的组成部分;
该方式主要用于程序拼接SQL;
如果sql中使用${OGNL},并且参数的类型是(integer,string…)那么OGNL表达式可以写成任意东西;
- ${OGNL}表达式的应用场景
1)不能用在登录,会出现sql注入
比如登录功能,在输入name的时候可以人为的制造成功:
User user=new User();
user.setName("\"admin\" or \"1=1\" ");
user.setPassword("\"admin\" ");
定义SQL: select id,name,password from t_user where name = ${name} and password=${password}
最终SQL: select id,name,password from t_user where name="test" or "1=1" and password="test" 出现sql注入
2)用在order by + limit的场景
七、常用的动态SQL
- 传入id批量删除
<!-- delete from product where id in ( ? , ? ) 批量删除:
collection="list":传入的list,相当于map的key,通过list得到传入的整个集合的值;
index="index" :每次循环的索引
item="id" :每次循环的这个值
open="(" :以什么开始
separator=",":分隔符
close=")":以什么结束
-->
<delete id="removeMany" parameterType="java.util.ArrayList" >
DELETE FROM product WHERE id IN <foreach collection="list"
open="(" close=")"
separator="," item="id">
#{id}
</foreach>
</delete>
实现方法
@Override
public void removeMany(List<Long> ids) {
System.out.println("=======================");
SqlSession sqlSession =null;
try {
sqlSession = MybatisUtil.INSTANCE.openSqlSession();
sqlSession.delete("com.lr.mybatis._04_batch_many.domain.ProductMapper.removeMany", ids);
//提交事务
sqlSession.commit();
}catch (Exception e){
//事务回滚
sqlSession.rollback();
e.printStackTrace();
}finally {
MybatisUtil.INSTANCE.endSqlSession(sqlSession);
}
}
- 批量添加
<!-- 批量插入:
insert into product(productName,salePrice,costPrice,cutoff,supplier,brand,dir_id)
values (?,?,?,?,?,?,?) , (?,?,?,?,?,?,?)
parameterType:传入参数是:java.util.List的类型的;
对传入的list进行foreach循环:取出值填入插入的values中:
collection="list":这个不能修改,表示传入的一个list类似于一个key是list,得到传入的list集合
index="index":每一次遍历的序号
item="item":每次遍历的这个对象别名,可以修改
separator=",":每遍历一次后的分隔符
-->
<insert id="addMany" parameterType="java.util.ArrayList" >
INSERT INTO product (productName,brand,supplier,salePrice,costPrice,cutoff,dir_id)VALUES
<foreach collection="list" item="product" separator=",">
(#{product.productName},#{product.brand},#{product.supplier},#{product.salePrice},
#{product.costPrice},#{product.cutoff},#{product.dir_id})
</foreach>
</insert>
实现方法
@Override
public void addMany(List list) {
SqlSession sqlSession =null;
try {
sqlSession = MybatisUtil.INSTANCE.openSqlSession();
sqlSession.delete("com.lr.mybatis._04_batch_many.domain.ProductMapper.addMany", list);
//提交事务
sqlSession.commit();
}catch (Exception e){
//事务回滚
sqlSession.rollback();
e.printStackTrace();
}finally {
MybatisUtil.INSTANCE.endSqlSession(sqlSession);
}
}