2.问题:
(1)sql写在JAVA代码中,修改sql必须修改代码,耦合度太高;
(2)代码量多,重复性代码过多;
(3)手动设置参数,并且如果是查询操作,还需要手动转换结果,麻烦;
(4)连接资源手动关闭,麻烦【以后和Spring整合就不需要手动关闭连接了】;
1. MyBatis是一个ORM的数据库持久化框架;
2. Mybatis底层还是原生的JDBC代码,对JDBC代码的封装;
ORM:对象关系映射(Object Relational Mapping,简称ORM):是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术;
ORM框架操作数据库关系方式有很多种,常用的有两种:
第一种:Sql操作方式(半映射或半自动):把SQL配置到配置文件中,通过不同SQL中完成对象实体和数据库关系相互转换的操作(Mybatis的实现方式)
第二种:完整映射:直接使用对象实体和数据库关系进行映射,不用写SQL(简单的操作),由框架自己生成(JPA、Hibenate实现方式)
面试题:Mybatis相较于jdbc的优点?
1.把sql语句从java代码中抽取出来,方便维护,并且修改sql时不用修改java代码;
2. 不用手动设置参数和对结果集的处理,让我们操作数据库更加简单;
3. 与JDBC相比,大大减少了代码量,提高了开发效率;
1.创建一个Product对象,和数据库的表对应
2.类的名称和类型都和我们的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...
}
/**
* 商品的持久操作
* @author Administrator
*/
public interface IProductDAO {
/**
* 添加一个商品
*/
void save(Product p);
/**
* 更新一个商品
*/
void update(Product p);
/**
* 删除一个商品
*/
void delete(Long id);
/**
* 得到一个商品
*/
Product get(Long id);
/**
* 得到所有商品
*/
List<Product> findAll();
}
一切准备就绪。接下来就是开始使用MyBatis了。但是问题来了,怎么用呢?
查看文档:该文档虽然只有50多页,但是已经足够咱们学习了。
核心配置文件:MyBatis-Config.xml
<configuration>
<properties resource="db.properties">properties>
<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>
<mappers>
<mapper resource="cn/itsource/domain/ProductMapper.xml" />
mappers>
configuration>
属性文件:db.properties
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql:///test0303
db.username=root
db.password=admin
映射文件:
例如实体类有:
cn.itsource.domain.Product
映射文件名为:
cn/itsource/domain/ProductMapper.xml
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.itsource.dao.IProductDao">
<select id="get" parameterType="long" resultType="cn.itsource.domain.Product">
select * from product where id = #{id}
select>
mapper>
入门代码:
@Override
public Product get(Long id) {
SqlSession session = null;
try {
Reader reader = Resources.getResourceAsReader("MyBatis-Config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
session = sqlSessionFactory.openSession();
// 两个参数:mapper的nameSpace+id的路径,和需要的参数
return session.selectOne(NAME_SPACE+"get", id);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("使用get方法出错:" + e.getMessage());
} finally {
if (session != null) {
session.close();
}
}
}
package cn.itsource.mybatis._01_hello.dao.impl;
import cn.itsource.mybatis._01_hello.dao.IProductDao;
import cn.itsource.mybatis._01_hello.domain.Product;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.Reader;
import java.util.List;
/**
* dao实现
*
* insert:添加,事务控制
* delete:删除,事务控制
* update:修改,事务控制
* selectOne:查询一个
* SelectList:查询多个
*/
public class ProductDaoImpl implements IProductDao {
@Override
public void save(Product product) {
SqlSession sqlSession = null;
try {
//1 准备配置文件 ok
//2 创建SqlSessionFactory
Reader reader = Resources.getResourceAsReader("MyBatis-Config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
//3 获取sqlSession做操作
sqlSession = sqlSessionFactory.openSession();
//表示调用那句sql(namespace+.+id)-拷贝,传入参数,接收得到返回值
sqlSession.insert("cn.itsource.dao.IProductDao.save",product);
//提交事物-增删改,数据库存储引擎不能是myIsam,它不支持事务
sqlSession.commit();
}catch (Exception e){
e.printStackTrace();
sqlSession.rollback();
}finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
@Override
public void remove(Long id) {
SqlSession sqlSession = null;
try {
//1 准备配置文件 ok
//2 创建SqlSessionFactory
Reader reader = Resources.getResourceAsReader("MyBatis-Config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
//3 获取sqlSession做操作
sqlSession = sqlSessionFactory.openSession();
//表示调用那句sql(namespace+.+id)-拷贝,传入参数,接收得到返回值
sqlSession.delete("cn.itsource.dao.IProductDao.remove",id);
//提交事物-增删改,数据库存储引擎不能是myIsam,它不支持事务
sqlSession.commit();
}catch (Exception e){
e.printStackTrace();
sqlSession.rollback();
}finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
@Override
public void update(Product product) {
SqlSession sqlSession = null;
try {
//1 准备配置文件 ok
//2 创建SqlSessionFactory
Reader reader = Resources.getResourceAsReader("MyBatis-Config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
//3 获取sqlSession做操作
sqlSession = sqlSessionFactory.openSession();
//表示调用那句sql(namespace+.+id)-拷贝,传入参数,接收得到返回值
sqlSession.update("cn.itsource.dao.IProductDao.update",product);
//提交事物-增删改,数据库存储引擎不能是myIsam,它不支持事务
sqlSession.commit();
}catch (Exception e){
e.printStackTrace();
sqlSession.rollback();
}finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
@Override
public Product loadById(Long id) {
SqlSession sqlSession = null;
try {
//1 准备配置文件 ok
//2 创建SqlSessionFactory
Reader reader = Resources.getResourceAsReader("MyBatis-Config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
//3 获取sqlSession做操作
sqlSession = sqlSessionFactory.openSession();
//表示调用那句sql(namespace+.+id)-拷贝,传入参数,接收得到返回值
return sqlSession
.selectOne("cn.itsource.dao.IProductDao.loadById",id);
}catch (Exception e){
e.printStackTrace();
}finally {
if (sqlSession != null) {
sqlSession.close();
}
}
return null;
}
@Override
public List<Product> loadAll() {
SqlSession sqlSession = null;
try {
//1 准备配置文件 ok
//2 创建SqlSessionFactory
Reader reader = Resources.getResourceAsReader("MyBatis-Config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
//3 获取sqlSession做操作
sqlSession = sqlSessionFactory.openSession();
//表示调用那句sql(namespace+.+id)-拷贝,传入参数,接收得到返回值
return sqlSession
.selectList("cn.itsource.dao.IProductDao.loadAll");
}catch (Exception e){
e.printStackTrace();
}finally {
if (sqlSession != null) {
sqlSession.close();
}
}
return null;
}
}
package cn.itsource.mybatis._01_hello.dao;
import cn.itsource.mybatis._01_hello.dao.impl.ProductDaoImpl;
import cn.itsource.mybatis._01_hello.domain.Product;
import org.junit.Test;
/**
* 打印快捷方式:obj.sout
* 快速省略测试:类里面-右键-goto-junit4-勾上
*/
public class IProductDaoTest {
IProductDao productDao = new ProductDaoImpl();
@Test
public void save() {
Product product = productDao.loadById(18L);
product.setId(null);
product.setProductName("yhptest......");
productDao.save(product);
}
@Test
public void remove() {
Product product = productDao.loadById(19L);
System.out.println(product);
productDao.remove(19L);
product = productDao.loadById(19L);
System.out.println(product);
}
@Test
public void update() {
Product product = productDao.loadById(22L);
System.out.println(product);
product.setProductName("yhptest......edit");
productDao.update(product);
product = productDao.loadById(22L);
System.out.println(product);
}
@Test
public void loadById() {
//obj.sout
System.out.println(productDao.loadById(1L));
}
@Test
public void loadAll() {
for (Product product : productDao.loadAll()) {
System.out.println(product);
}
}
}
SqlSessionFactoryBuilder:
建造者模式:我们最后拿到的这个对象是非常复杂的. 用这个建造者就它先为我们把这些复杂的代码完成。这个类可以被实例化,使用和丢弃。一旦你创建了SqlSessionFactory后,这个类对象就不需要存在了。因此SqlSessionFactoryBuilder实例的最佳范围是方法范围(也就是本地方法变量)。你可以重用SqlSessionFactoryBuilder来创建多个SqlSessionFactory实例,但是最好的方式是不需要保持它一直存在来保证所有XML解析资源,因为还有更重要的事情要做;
SqlSessionFactory:
一旦被创建,SqlSessionFactory应该在你的应用执行期间都存在。没有理由来处理或重新创建它。使用SqlSessionFactory的最佳实践是在应用运行期间不要重复创建多次。这样的操作将被视为是非常糟糕的。因此SqlSessionFactory的最佳范围是应用范围。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。然而这两种方法都不认为是最佳实践。这样的话,你可以考虑依赖注入容器,比如Google Guice或Spring。这样的框架允许你创建支持程序来管理单例SqlSessionFactory的生命周期;
SqlSession
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能被共享,也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段甚至是实例字段中。也绝不能将SqlSession实例的引用放在任何类型的管理范围中,比如Serlvet架构中的HttpSession。如果你现在正用任意的Web框架,要考虑SqlSession放在一个和HTTP请求对象相似的范围内。换句话说,基于收到的HTTP请求,你可以打开了一个SqlSession,然后返回响应,就可以关闭它了。关闭Session很重要;
sql
代码
sql
代码
注意:添加的时候一定要记住提交事务(配置事务、表结构支持事务)
sql
代码
sql
代码
sql
代码
引入:之前通过Mybatis操作数据库的时候,除了写接口,还要写实现类。MyBatis基于动态代理机制,让我们无需再编写Dao的实现,但是必须要遵循一定的规范:
映射文件:
<mapper namespace="cn.itsource.mybatis.mapper.UserMapper">
<select id="接口的方法名" parameterType="long" resultType="User">
Select * from t_user where id = #{id}
select>
mapper>
XxxMapper写法:
public interface ProductMapper {
//自增长ID
void insert1(Product product);
}
XxxMapper.xml写法:
<!-- 插入数据并返回自增ID
有自增ID功能数据库可以采用useGeneratedKeys="true"开启判断是否是自增ID
keyProperty="id" 指定插入数据后自增ID返回时赋值给实体类的那个属性(这里是id属性)
-->
<!-- void insert1(Product product) -->
<insert id="insert1" useGeneratedKeys="true" keyProperty="id">
insert into product(productName) values(#{productName})
</insert>
测试:
@Test
public void testInsert(){
SqlSession session = MybatisUtil.getSqlSession();
ProductMapper mapper = session.getMapper(ProductMapper.class);
Product product = new Product();
product.setProductName("绿茶111");
System.out.println("====执行前===="+product.getId());
mapper.insert1(product);
System.out.println("====执行后===="+product.getId());
session.commit();
session.close();
}
1.通过mysql函数获取到刚插入记录的自增主键:LAST_INSERT_ID(),但是是在insert之后调用此函数。
XxxMapper写法:
public interface ProductMapper {
//自增长ID
void insert2(Product product);
}
XxxMapper.xml写法:
<!-- void insert2(Product product) -->
<insert id="insert2">
<!-- 将插入数据的主键返回,返回到product对象中
SELECT LAST_INSERT_ID():得到刚insert进去记录的主键值,只适用于自增主键
keyProperty:将查询到主键值设置到parameterType指定的对象的那个
resultType:指定SELECT LAST_INSERT_ID()的结果类型selectKey中resultType属性指定期望主键的返回的数据类型,
keyProperty属性指定实体类对象接收该主键的字段名
order属性指定执行查询主键值SQL语句是在插入语句执行之前还是之后(可取值:after和before)
注:mysql用after,oracle用before
-->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Long">
SELECT LAST_INSERT_ID()
</selectKey>
insert into product(productName) values(#{productName})
</insert>
测试:
@Test
public void testInsert(){
SqlSession session = MybatisUtil.getSqlSession();
ProductMapper mapper = session.getMapper(ProductMapper.class);
Product product = new Product();
product.setProductName("绿茶111222");
System.out.println("====执行前===="+product.getId());
mapper.insert2(product);
System.out.println("====执行后===="+product.getId());
session.commit();
session.close();
}
在使用MyBatis的很多时候,我们需要把日志打印出来,帮助我们进行分析与排错。特别是大家现在学习阶段,要求大家都MyBatis的日志打开;
#日志器logger #輸出器appender #布局器layout
#1.控制台输出
#指定日志器的输出级别和日志器的名称
#log4j.rootLogger=info,myconsole
#指定输出器
#log4j.appender.myconsole=org.apache.log4j.ConsoleAppender
#指定布局器
#log4j.appender.myconsole.layout=org.apache.log4j.SimpleLayout
#2.文件输出.txt
#指定日志器的输出级别和日志器的名称
#log4j.rootLogger=error,myfile
#指定输出器
#log4j.appender.myfile=org.apache.log4j.FileAppender
#log4j.appender.myfile.File=E:\\log4j.txt
#指定布局器(普通布局表示文本输出)
#log4j.appender.myfile.layout=org.apache.log4j.SimpleLayout
#3.文件输出.html
#指定日志器的输出级别和日志器的名称
#log4j.rootLogger=error,myhtml
#指定输出器
#log4j.appender.myhtml=org.apache.log4j.FileAppender
#log4j.appender.myhtml.File=D:\\log4j.html
#指定布局器(网页布局)
#log4j.appender.myhtml.layout=org.apache.log4j.HTMLLayout
#4.控制台输出+文件输出.txt
#指定日志器的输出级别和日志器的名称
#log4j.rootLogger=error,con,file
#指定输出器
#log4j.appender.con=org.apache.log4j.ConsoleAppender
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=D\:\\log4j.txt
#指定布局器(网页布局)
#log4j.appender.con.layout=org.apache.log4j.SimpleLayout
#log4j.appender.file.layout=org.apache.log4j.SimpleLayout
#5.控制台输出+自定义布局
log4j.rootLogger=DEBUG,my
#指定输出器
log4j.appender.my=org.apache.log4j.ConsoleAppender
#指定布局器(自定义布局)
#指定布局为自定义布局
log4j.appender.my.layout=org.apache.log4j.PatternLayout
#指定在自定义布局的格式,%d -- 表示当前系统时间,%t -- 执行该业务的线程名称,%p -- 日记器的级别,-5 -- 5表示输出字符的个数,符号表示右对齐
#%c -- 表示指定业务所在的类的完全限定名(包名.类名),%m -- 输出额外信息,%n -- 表示换行
log4j.appender.my.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
#设置package(可以是自定义的包也可以是api的包)输出级别
log4j.logger.org.springframework=info
log4j.logger.cn.itsource=debug
注意:在上面的标红部分是我们需要修改的,它大概表示为当前会打印哪些位置下面文件的日志;如果配置成功,那么在MyBatis运行操作数据时就可以看到相应的日志了;
<typeAliases>
<typeAlias type="cn.itsource.domain.Dept" alias="Dept" />
<package name="cn.itsource.domain" />
typeAliases>
MyBatis会把这个表达式使用?(占位符)替换,作为一个sql参数使用:推荐使用。比如name的值为:
定义SQL: select * from t_user where name = #{name}
最终SQL: select * from t_user where 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注入
注意:除了要拼接sql结构体要用$(是直接拼接sql),其他都用#,因为用#会替换为?,最终通过PreparedStament来设置参数,不会有sql注入问题;
Mybatis与Hibernate区别?