一、框架:
半成品,可以理解为房子的大框,之后砌墙会在其基础上。
二、简介
MyBatis 本是[apache]的一个开源项目[iBatis], 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的[持久层]框架。
Mybatis是一个持久层框架。代替JDBC技术完成与数据库交互功能的框架,操作过程中,调用底层封装好的API,代码会被解析成JDBC源码进行功能实现。
三、优点
1、基于SQL语法的,简单易学。
2、jdbc将sql语句写在java语句中,属于硬编码,而框架把SQL语句被封装在配置文件中,方便程序的维护,降低程序的耦合度。
3、程序调试方便。
4、jdbc需要将查询结果映射到实体类中,而框架可以省去这一步骤,查询即得到映射好的对象。
四、框架的搭建:
创建一个普通的JAVA工程。
引入jar包
Mybatis-3.2.7.jar:Mybatis的核心jar包
Ojdbc14.jar:驱动Jar
Commons-logging-1.1.1.jar
Log4j.jar:记录日志的jar
Log4j.properties 消息资源文件
3、在src中引入SqlMapConfig.xml文件(名字可随便取,但惯例都叫此,之后会有专门的地方根据名字读取文件),现在专门解释一下该文件,文件后缀名时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">
是处理指令,能够解析MyBatis元素
③根元素
<configuration></configuration>
一个配置文件只能有一个根元素
④在<configuration></configuration>中配置数据源,所谓数据源就是数据的来源,可以理解为数据库
<environments default="oracle">
<environment id="oracle">
<!-- 事务管理器 -->
<transactionManager type="jdbc"></transactionManager>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:ORCL"/>
<property name="username" value="scott"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
a.default中的值决定具体使用哪一数据源,所谓数据源就是数据的来源,即数据库
b.在指定的数据源中,首先配置事务控制器
c.配置数据源的相关信息,POOLED表示使用连接池进行连接
d.property中的name都有特定的含义,不允许改变
4、建表语句
-- Create table
create table T_USER
(
id NUMBER not null,
username VARCHAR2(20),
hiredate DATE,
password VARCHAR2(20)
)
tablespace USERS
pctfree 10
initrans 1
maxtrans 255
storage
(
initial 64K
next 1M
minextents 1
maxextents unlimited
);
-- Create/Recreate primary, unique and foreign key constraints
alter table T_USER
add constraint PK_T_USER_ID primary key (ID)
using index
tablespace USERS
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 64K
next 1M
minextents 1
maxextents unlimited
);
创建实体类
public class User {
private int id;
private String username;
private String password;
private Date hiredate;
}
5、创建接口UserMapper
public interface UserMapper {
User getUser(int id);
}
6、创建与UserMapper接口同名的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命名空间,作用就是对sql进行分类化管理-->
<mapper namespace="com.dao.UserMapper">
<select id="getUser" parameterType="java.lang.Integer" resultType="com.bean.User">
select * from t_user where id = #{value}
</select>
</mapper>
①mapper是根元素
②<mapper namespace="com.dao.UserMapper">的namespace值必须写接口的全限定名,
③根元素中可以编写sql语句
④parameterType:指定输入参数类型,mybatis 从输入对象中获取参数值拼接在 sql 中。
resultType:指定输出结果类型,mybatis 将 sql 查询结果的一行记录数据映射为 resultType 指定类型的对象。
上述参数值需要引入全限定名
7、有的童鞋可能猜到了,你可以认为这个配置文件类似于接口的实现类,只不过我们不用implements关键字,而是在主配置文件中读取它
<environments default="oracle">
</environments>
<mappers>
<!-- <mapper resource="com/dao/UserMapper.xml"/> -->
<package name="com.dao"/>
</mappers>
8最后,我们编写测试类,主要经过如下步骤
①加载核心配置文件
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
②获取数据库连接池对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
③获取与数据库的链接
SqlSession session = factory.openSession();
④通过连接拿到实体类对象
UserMapper userMapper = session.getMapper(UserMapper.class);
此时相关数据已经封装给了实体类
五、引入配置文件
在classpath下创建db.properties
jdbc.driver =oracle.jdbc.driver.OracleDriver
jdbc.url = jdbc:oracle:thin:@localhost:1521:neuedu
jdbc.username=scott
jdbc.password=tiger
此时在SqlMapConfig.xml文件中需要修改为
<!-- properties元素:
resource属性,要引入的消息资源文件相对于src的路径 -->
<properties resource="db.properties"></properties>
<environments default="oracle">
<environment id="oracle">
<!-- 事务管理器 -->
<transactionManager type="jdbc"></transactionManager>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
六、关于Mybatis的API
1、SqlSession
SqlSession 中封装了对数据库的操作,如:查询、插入、更新、删除等。
2、SqlSessionFactoryBuilder
SqlSessionFactoryBuilder 用于创建 SqlSessionFacoty,SqlSessionFacoty 一旦创建完成就不需要SqlSessionFactoryBuilder 了,因为 SqlSession 是通过 SqlSessionFactory 生产,所以可以将SqlSessionFactoryBuilder 当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。
3、SqlSessionFactory
SqlSessionFactory 是一个接口,接口中定义了 openSession 的不同重载方法,SqlSessionFactory 的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理 SqlSessionFactory。
public class SqlSessionFactoryUtil {
//首先创建静态成员变量sqlSessionFactory,静态变量被所有的对象所共享。
public static SqlSessionFactory sqlSessionFactory = null;
public static SqlSessionFactory getSqlSessionFactory() {
//如果sqlSessionFactory没有被创建就读取全局配置文件,假如已经被创建过了,就使用已经存在的sqlsessionfactory。
//这样就有了单例模式的效果
if(sqlSessionFactory==null){
String resource = "SqlMapConfig.xml";
try {
Reader reader = Resources.getResourceAsReader(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return sqlSessionFactory;
}
}
七、类别名
1、以上提到,在xml文件中配置SQL语句时,需要使用全限定名,因此非常不便,需要配置类别名,使用typeAliases元素
<typeAliases>
<!-- typeAlias元素
type,全限定名
alias,类别名
-->
<typeAlias alias="User" type="com.bean.User"/>
</typeAliases>
此时查询就可以使用User代替com.bean.User,注意,该元素必须在environments元素以前写,因为xml文件对元素顺序有要求
2、内置别名
java中的类型可以直接使用,无需配置别名,如int,long,String,List,Map
八、核心配置文件
1、defualt属性
现在,让我们来说一说environments元素的default属性,这是一个必须有的属性,由于系统可能配置多个数据源,因此需要指定一个默认数据源,default的值与environment的id相对应,那就走该id所在的数据源
<environments default="oracle">
<environment id="oracle">
</environment>
<environment id="oracle1">
</environment>
</environments>
还有一种情况,若我default的值指定了某一个数据源,是不是就连不了别的数据源?答案是否定的,SqlSessionFactoryBuilder有一个重载的方法能够指定连接其他数据源,如下代码所示
<environments default="oracle">
<environment id="oracle">
</environment>
<environment id="oracle1">
<transactionManager type="jdbc"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="kkk"/>
</dataSource>
</environment>
</environments>
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader,"oracle1");
由于我在oracle1中密码错误,因此登录失败
2、事务管理器transactionManager
type属性,指定事务管理方式,可选值有
jdbc:利用传统jdbc方式进行事务处理,需要手动提交、回滚
MANAGED:让容器进行事务处理,此处容器指Spring容器,若配成此选项,意味着Mybatis不进行事务管理,交给Spring
3、dataSource元素
type属性,指定配置数据源连接的方式,可选择的值如下
POOLED,使用数据库连接池,使用能够存放多个已经生成好的数据库连接的区域,好处在于想拿连接时只需要open一下就可以拿到连接,不涉及对连接的管理
UNPOOLED,不使用数据库连接池,即使用原始连接方式,需要对连接管理
JNDI,使用第三方数据库连接池,POOLED表示使用Mybatis自己的连接池,如果想使用第三方,就选这一项,常见提供数据库连接池的第三方有tomcat自带的连接池,Spring容器
4、mappers
将应用中所有SQL映射文件配置到核心配置文件中,目的是加载核心配置文件时能够加载SQL映射文件,xml文件与以往java文件不同,java文件编译成字节码后,其他方法就可以调用其中的方法,想用xml文件必须加载
5、其他元素
如settings,可以用来优化配置,最多可以连几个连接,或同时最多可以执行几个SQL语句,几乎不常用,而且真正开发时也不需要你去做
九、SQL映射文件
我们之前拿最简单的查询做了例子演示框架搭建,现在,让我们看一下复杂的查询以及增删改如何去做,一般情况下,我们都是一个SQL映射文件对应一张表和一个实体类,有关该实体类的增删改查都在同一个映射文件中
1、回顾一下最简单的查询
select元素相关属性
id,必须存在的属性,值从概念上讲是唯一标示,不能随便写,应对应接口中方法的名
parameterType,若该方法有参,则必须有该属性,值和接口中参数的类型相对应
resultType,结果集的类型,经过查询,数据库中的信息自动封装,指定类型,就告诉框架你把信息封装成一个什么类型的对象
2、多个参数
需求:根据用户名和编号进行查询
接口中增加方法
User selectUser(String username,int id);
发现parameterType无法指定类型,
①加注解
User selectUser(@Param("username")String username,@Param("id")int id);
<select id="selectUser" resultType="User">
select * from t_user where username = #{username} and id = #{id}
</select>
②参数整合在一起,使用bean对象,
List<User> selectUser(User user);
<select id="selectUser" parameterType="User" resultType="User">
select * from t_user where username = #{username} and id = #{id}
</select>
注意,查询结果有多条,使用list集合作为返回值类型,resultType指定集合中元素的类型
3、删除
需求:根据id进行删除
void deleteUser(int id);
<delete id="deleteUser" parameterType="int">
delete from t_user where id = #{value}
</delete>
去测试类中执行发现并没有生效,没有自动提交,因此需要在try中调用session的commit方法,在catch中调用session的rollback方法
4、修改
void updateUser(User user);
<update id="updateUser" parameterType="User">
update t_user set username = #{username},password = #{password}
where id = #{id}
</update>
5、插入
<!-- keyProperty属性主键列对应的属性的名称
order属性BEFORE表示子查询在主查询前
-->
<insert id="insertUser">
insert into t_user values(#{id},#{username},sysdate,#{password})
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select max(id)+1 from t_user
</selectKey>
</insert>
void insertUser(User user);
User user = new User();
user.setUsername("k");
user.setPassword("k");
userMapper.insertUser(user);
session.commit();
十、特殊结果、参数
1、特殊参数类型使用
根据用户名和密码查询,比如此时用户名和密码不同时写在同一个实体类中,此时要使用一个参数就要用到Map
User selectUserByNAP(Map<String,String> map);
<select id="selectUserByNAP" parameterType="map" resultType="User">
select * from t_user where username = #{m_username} and password = #{m_password}
</select>
Map<String,String> map = new HashMap<String,String>();
map.put("m_username", "l");
map.put("m_password", "l");
User user = userMapper.selectUserByNAP(map);
System.out.println(user.getPassword());
sql语句中参数等于Map的键
2、特殊结果集使用
如果实体类属性与数据库列名不一样,就使用resultMap作为结果集类型
User selectUserByNAP1(Map<String,String> map);
<select id="selectUserByNAP1" parameterType="map" resultMap="userres">
select * from t_user where username = #{m_username} and password = #{m_password}
</select>
<resultMap type="User" id="userres">
<result column="password" property="password"></result>
</resultMap>
Map<String,String> map = new HashMap<String,String>();
map.put("m_username", "l");
map.put("m_password", "l");
User user = userMapper.selectUserByNAP1(map);
System.out.println(user.getPassword());
type:查询结果最终得到的类型
3、建employee和department两张表,其中employee表有eid、eno、ename、deptid等字段,department表有did、dno、dname等字段
-- Create table
create table T_DEPARTMENT
(
did NUMBER(4) not null,
dno VARCHAR2(20),
dname VARCHAR2(20)
)
tablespace USERS
pctfree 10
initrans 1
maxtrans 255
storage
(
initial 64K
next 1M
minextents 1
maxextents unlimited
);
-- Create/Recreate primary, unique and foreign key constraints
alter table T_DEPARTMENT
add constraint PK_DID_DEPARTMENT primary key (DID)
using index
tablespace USERS
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 64K
next 1M
minextents 1
maxextents unlimited
);
-- Create table
create table T_EMPLOYEE
(
eid NUMBER(4) not null,
eno VARCHAR2(20),
ename VARCHAR2(20),
deptid NUMBER(4)
)
tablespace USERS
pctfree 10
initrans 1
maxtrans 255
storage
(
initial 64K
next 1M
minextents 1
maxextents unlimited
);
-- Create/Recreate primary, unique and foreign key constraints
alter table T_EMPLOYEE
add constraint PK_EID_EMPLOYEE primary key (EID)
using index
tablespace USERS
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 64K
next 1M
minextents 1
maxextents unlimited
);
alter table T_EMPLOYEE
add constraint FK_DEPTID_EMPLOYEE foreign key (DEPTID)
references T_DEPARTMENT (DID);
public class Employee {
private int eid;
private String eno;
private String ename;
private Department dept;
}
public class Department {
private int did;
private String dno;
private String dname;
private List<Employee> employees;
}
十一、多表查询
1、一对一关联
如果要做查询员工信息同时将部门信息同时查出来
(1)嵌套子查询
public Employee getEmployeeById(int eid);
<select id="getEmployeeById" parameterType="int" resultMap="empres">
select * from t_employee where eid = #{eid}
</select>
<!-- 嵌套子查询
association元素
property属性嵌套查询的结果赋值给Employee中属性的名字
column属性外键字段的名称
javaType属性嵌套子查询返回结果集的类型
select属性嵌套的子查询对相应查询
-->
<resultMap type="Employee" id="empres">
<association property="dept" column="deptid" javaType="Department" select="getDeptById"></association>
</resultMap>
<select id="getDeptById" parameterType="int" resultType="Department">
select * from t_department where did = #{deptid}
</select>
association元素
property属性嵌套查询的结果赋值给Employee中属性的名字
column属性外键字段的名称
javaType属性嵌套子查询返回结果集的类型
select属性嵌套的子查询对相应查询
以上查询的做法实际上非常不好,仅做了一个多表查询就需要多次操作数据库
(2)嵌套结果
public Employee getEmployeeByNo(String eno);
<select id="getEmployeeByNo" parameterType="string" resultMap="empRes">
select *
from t_employee e,t_department d
where e.deptid = d.did
and e.eno = #{eno}
</select>
<resultMap type="employee" id="empRes">
<association property="dept" column="deptid" javaType="Department" resultMap="deptRes"></association>
</resultMap>
<resultMap type="department" id="deptRes">
<result property="did" column="did"></result>
<result property="dno" column="dno"></result>
<result property="dname" column="dname"></result>
</resultMap>
EmployeeDao employeeDao = session.getMapper(EmployeeDao.class);
Employee employee = employeeDao.getEmployeeByNo("1");
System.out.println(employee.getDept().getDid());
2、一对多关联
查询部门信息,并将该部门所有员工查询出来
Department getDepartmentById(int did);
<select id="getDepartmentById" parameterType="int" resultMap="deptRes">
select *
from t_employee e,t_department d
where e.deptid = d.did
and d.did = #{did}
</select>
<resultMap type="department" id="deptRes">
<result property="did" column="did"></result>
<result property="dno" column="dno"></result>
<result property="dname" column="dname"></result>
<!-- 一对多的关联查询
collection元素
property属性针对多个员工信息封装出的集合容器类型的对象,赋值给department类中的对应的属性
ofType属性集合中每个元素的类型 -->
<collection property="employees" ofType="employee">
<result property="eid" column="eid"></result>
<result property="eno" column="eno"></result>
<result property="ename" column="ename"></result>
</collection>
</resultMap>
DepartmentDao departmentDao = session.getMapper(DepartmentDao.class);
Department department = departmentDao.getDepartmentById(1);
List<Employee> list = department.getEmployees();
System.out.println(list.get(0).getEid());
3、向员工表中插入一条数据,同时指定其部门信息
<insert id="insert" parameterType="employee">
insert into t_employee values (#{eid},#{eno},#{ename},#{dept.did})
<selectKey keyProperty="eid" resultType="int" order="BEFORE">
select max(eid)+1 from t_employee
</selectKey>
</insert>
void insert(Employee e);
Employee employee = new Employee();
employee.setEno("2");
employee.setEname("Lily");
Department department = new Department();
department.setDid(1);
employee.setDept(department);
EmployeeDao employeeDao = session.getMapper(EmployeeDao.class);
employeeDao.insert(employee);
session.commit();
4、分页查询
利用pageHelper插件
首先引入jar包
jsqlparser-0.9.jar
pagehelper-3.6.4.jar
在核心配置文件中加入
<!-- plugins元素加在typeAliases下 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- property元素指明和哪个数据库匹配使用 -->
<property name="dialect" value="oracle"></property>
</plugin>
</plugins>
在查询映射文件中加入
<select id="getAll" resultType="user">
select * from t_user
</select>
List<User> getAll();
//获取第一页数据,每页显示1条
Page page = PageHelper.startPage(1, 1);
userMapper.getAll();
System.out.println("共"+page.getTotal()+"条记录");
List<User> list = page.getResult();
for(User u:list){
System.out.println(u.getPassword());
}
5、组合查询
<!-- 条件查询where元素,补齐where关键字 -->
<select id="getByCondition" parameterType="user" resultType="user">
select * from t_user
<!-- where 1=1 -->
<where>
<if test="id != null and id != 0">
and id = #{id}
</if>
<if test="username != null and username != ''">
and username = #{username}
</if>
</where>
</select>
6、批量删除
<!-- 参数是数组,无需加参数
foreach元素遍历集合容器对象
collection属性指定的是方法传入的类型
item属性每次遍历到的元素
open属性遍历得到的结果的开始字符
separator属性元素之间的分隔符
-->
<delete id="deleteUsers">
delete from t_user where id in
<foreach collection="array" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</delete>
void deleteUsers(int[] ids);
int[] i = new int[2];
i[0] = 3;
i[1] = 4;
userMapper.deleteUsers(i);
session.commit();
7、缓存
User user = userMapper.getUser(1);
user.setPassword("k");
//修改查询到的user信息
System.out.println(user.getPassword());
user.setPassword("j");
//再次查询理论上应该得到数据库中的内容,但是却是上述修改后的内容,这是因为Mybatis缓存
//缓存是因为提高性能
user = userMapper.getUser(1);
System.out.println(user.getPassword());
此时需要在sql映射文件中加入flushCache="true"
<select id="getUser" parameterType="java.lang.Integer" resultType="User" flushCache="true">
select * from t_user where id = #{value}
</select>
当查询数据库返回结果时,mybatis会生成key、value形式的对象存在于缓存,在接下来在内存中针对查询结果做变更,相当于改变了缓存中的数据,再次查询不访问数据库,而是直接采用缓存中的数据