文章目录
主要是看视频学习之后,做个笔记,可能有错误的地方
1 三层架构
表现层:用于展示数据
业务层:用于处理业务需求
持久层:用于和数据库交互
2 Mybatis介绍
mybatis是一个持久层框架,内部封装了JDBC,开发者只需要关注sql语句本身即可
开发者可以使用xml配置或者注解的方法配置各种statement
采用ORM思想(Object Relation Mapping 对象关系映射),将数据库表与实体类一一对应,开发者操作实体类即可实现操作数据库表
mybatis的dao实现方式
- 可以采用xml配置,mybatis自动实现dao实现类
- 可以采用注解配置,mybatis自动实现dao实现类
- mybatis也支持开发者自己写dao实现类
3 Maven中配置Mybatis
mybatis的版本号可以在mybatis的官网查询
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
4 主配置文件SqlMapConfig.xml
在resources目录下创建SqlMapConfig.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>
...
</configuration>
4.1 配置environments环境
<environments default="mysql">
<!-- 配置mysql的环境 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/java_study_mybatis"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
4.2 配置mappers映射
如果采用xml配置方式,使用resource属性表示xml文件路径
<mappers>
<mapper resource="com/zhq/study/mybatis/dao/IUserDao.xml"></mapper>
</mappers>
如果采用注解方法,使用class表示dao接口类的全限定类名
<mappers>
<mapper class="com.zhq.study.mybatis.dao.IUserDao"/>
</mappers>
指定dao接口所在的包,当指定了之后,就不需要再写mapper以及resource或者class
<mappers>
<package name="com.zhq.study.mybatis.dao"/>
</mappers>
4.3 配置properties标签
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/java_study_mybatis"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</properties>
也可以使用外部文件jdbcConfig.properties直接引入
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/eesy_mybatis
username=root
password=1234
<properties resource="jdbcConfig.properties"></properties>
注意事项:
- resource属性:指定配置文件位置,按照类路径写法
- url属性:也是指定配置文件位置,按照url路径的写法
配置数据库的时候直接可以使用name
<property name="driver" value="${driver}"/>
4.4 配置typeAliases别名
<typeAliases>
<!-- typeAlias用于配置别名,后面再引用的时候,就不需要实体类的全限定类名,只需要别名即可,别名不区分大小写 -->
<typeAlias type="com.zhq.study.mybatis.domain.User" alias="aaauser"></typeAlias>
<!-- 用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,类名就是别名,同样不区分大小写 -->
<package name="com.zhq.study.mybatis.domain"/>
</typeAliases>
4.5 开启延迟加载
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
4.6 开启缓存
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
5 domain实体类
public void User implements Serializable {
...
}
注意事项:
- 属性名称最好与数据库表中的名称相同(windows中mysql不区分大小写)即可直接映射,否则需要另外配置名称映射
- 生成getter()和setter()方法
- 生成toString()方法
6 dao实现方式
6.1 XML配置
dao接口持久层
public interface IUserDao {
...
List<User> findAll();
void saveUser(User user);
void updateUser(User user);
void deleteUser(Integer userId);
User findById(Integer userId);
List<User> findByName(String username);
int findTotal();
...
}
在resources目录下,以与dao接口类相同的目录,创建IUserDao.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">
对应的sql语句:
<mapper namespace="com.zhq.study.mybatis.dao.IUserDao">
<!-- 配置所有查询 -->
<select id="findAll" resultType="com.zhq.study.mybatis.domain.User">
select * from user;
</select>
<!-- 保存用户 -->
<insert id="saveUser" parameterType="com.zhq.study.mybatis.domain.User">
<!-- 配置插入操作后,获取插入数据的id -->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username, address, sex, birthday) values(#{username}, #{address}, #{sex}, #{birthday});
</insert>
<!-- 更新用户 -->
<update id="updateUser" parameterType="com.zhq.study.mybatis.domain.User">
update user set username=#{username}, address=#{address}, sex=#{sex}, birthday=#{birthday} where id=#{id};
</update>
<!-- 删除用户 -->
<delete id="deleteUser" parameterType="java.lang.Integer">
<!-- 只有一个参数,所以用占位符就可以 -->
delete from user where id=#{uid};
</delete>
<!-- 根据id查询用户 -->
<select id="findById" parameterType="java.lang.Integer" resultType="com.zhq.study.mybatis.domain.User">
select * from user where id=#{uid};
</select>
<!-- 根据名称模糊查询 -->
<select id="findByName" parameterType="string" resultType="com.zhq.study.mybatis.domain.User">
select * from user where username like #{name};
<!-- select * from user where username like '%${value}%'; 必须要写成value-->
</select>
<!-- 获取用户总条数 -->
<select id="findTotal" resultType="int">
select count(id) from user;
</select>
</mapper>
注意事项:
- mapper标签的namespace属性:对应dao接口类的全限定类名
- id属性:对应方法名
- parameterType属性:该方法的参数类型的全限定类名(如果是基本类型,则可以直接使用)
- resultType属性:该方法的返回值类型的全限定类名
- #{username}中的参数应该与实体类的属性名称相同,如果只有一个参数则占位符即可
- 模糊查询时,如果采用#{name}则调用的时候需要加上%,如果采用’%${value}%'则必须写value
如果实体类属性和数据库表字段名称不相同,则可以在IUserDao.xml中另外配置名称映射
<resultMap id="userMap" type="com.zhq.study.mybatis.domain.User">
<!-- 主键字段的对应 -->
<id property="userId" column="id"></id>
<!-- 非主键字段的对应 -->
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
</resultMap>
<!-- 当使用了上面这种对应映射方法时,需要用resultMap,而不是resultType -->
<select id="findAll" resultMap="userMap">
select * from user;
</select>
注意事项:
- 主键字段用id标签,非主键字段用result标签
- property属性:实体类属性名称
- column属性:数据库表字段名称,或者是数据库语句查询结果的字段名称
- 当使用了名称映射配置之后,不再使用resultType,而用resultMap
6.2 注解
不需要xml文件配置,dao接口持久层
public interface IUserDao {
...
@Select("select * from user;")
List<User> findAll();
@Insert("insert into user(username, address, sex, birthday) values(#{username},#{address},#{sex},#{birthday})")
void saveUser(User user);
@Update("update user set username=#{username}, sex=#{sex}, address=#{address}, birthday=#{birthday} where id=#{id}")
void updateUser(User user);
@Delete("delete from user where id=#{id}")
void deleteUser(Integer id);
@Select("select * from user where id=#{id}")
User findById(Integer userId);
@Select("select * from user where username like #{username}")
List<User> findUserByName(String username);
@Select("select count(*) from user")
int findTotalUser();
...
}
如果实体类属性和数据库表字段名称不相同,则可以在注解上加Results
@Select("select * from account")
@Results(id = "accountMap", value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "uid", property = "uid"),
@Result(column = "money", property = "money")})
List<Account> findAll();
@Select("select * from account where uid=#{id}")
@ResultMap(value = {"accountMap"})
List<Account> findAllByUid(Integer id);
注意事项:
- Results标签的id属性:提供一个id,在其他方法中可以直接使用注解ResultMap引用
- Result标签的id属性:表示该实体类属性是否是主键
6.3 自己写dao
mybatis也支持自己写dao实现
public class UserDaoImpl implements IUserDao {
private SqlSessionFactory factory;
public UserDaoImpl(SqlSessionFactory factory) {
this.factory = factory;
}
public List<User> findAll() {
// 1. 根据factory生成SqlSession对象
SqlSession session = factory.openSession();
// 2. 调用SqlSession中的方法,实现查询列表
List<User> users = session.selectList("com.zhq.study.mybatis.dao.IUserDao.findAll"); // 参数就是配置文件中的key
// 3. 释放资源
session.close();
return users;
}
...
session.insert("com.zhq.study.mybatis.dao.IUserDao.saveUser", user);
...
session.update("com.zhq.study.mybatis.dao.IUserDao.updateUser", user);
...
session.delete("com.zhq.study.mybatis.dao.IUserDao.deleteUser", userId);
...
User user = session.selectOne("com.zhq.study.mybatis.dao.IUserDao.findById", userId);
...
}
注意事项:
- session调用方法的参数就是对应要执行的dao接口方法在配置文件中的key
7 进阶操作
7.1 多条件组合查询
在domain中定义一个查询对象类QueryVo,将多个条件封装到一个实体中作为接口方法的参数
public class QueryVo {
private User user;
private List<Integer> ids;
...
}
IUserDao中定义查询语句
List<User> findUserByVo(QueryVo vo);
List<User> findUserByids(QueryVo vo);
IUserDao.xml中配置sql语句
<select id="findUserByVo" parameterType="com.zhq.study.mybatis.domain.QueryVo" resultType="com.zhq.study.mybatis.domain.User">
select * from user where username like #{user.username};
</select>
7.2 xml中其他标签
<!-- 用where语句 -->
<select id="findUserByCondition" resultType="com.zhq.study.mybatis.domain.User" parameterType="com.zhq.study.mybatis.domain.User">
select * from user
<where>
<if test="username != null">
and username=#{username}
</if>
<if test="sex != null">
and sex=#{sex}
</if>
</where>
</select>
<!-- 根据queryvo中的id集合实现查询用户列表 -->
<select id="findUserByids" resultType="com.zhq.study.mybatis.domain.User" parameterType="com.zhq.study.mybatis.domain.QueryVo">
select * from user
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
7.3 多表查询:一对一
例子:一个用户可以有多个账户,每个账户只属于一个用户
实体类,从表实体应该包含一个主表实体的对象引用
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
// 从表实体应该包含一个主表实体的对象引用
private User user;
}
如果采用xml配置
<!-- 定义封装account和user的resultMap -->
<resultMap id="accountUserMap" type="com.zhq.study.mybatis.domain.Account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 建立一对一的关系映射,配置封装user的内容 -->
<association property="user" column="uid">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
</association>
</resultMap>
<!-- 配置所有查询 -->
<select id="findAll" resultMap="accountUserMap">
select u.*, a.id as aid, a.uid, a.money from account a, user u where u.id=a.uid;
</select>
也可以通过另一个查询方法
<!-- 定义封装account和user的resultMap -->
<resultMap id="accountUserMap" type="com.zhq.study.mybatis.domain.Account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 建立一对一的关系映射,配置封装user的内容 -->
<association property="user" column="uid" select="com.zhq.study.mybatis.dao.IUserDao.findById"/>
</resultMap>
如果采用注解方式
public interface IAccountDao {
/* 查询所有账户,并获取对应的用户 */
@Select("select * from account")
@Results(id = "accountMap", value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "uid", property = "uid"),
@Result(column = "money", property = "money"),
@Result(property = "user", column = "uid",
one = @One(select = "com.zhq.study.mybatis.dao.IUserDao.findById", fetchType = FetchType.EAGER))})
List<Account> findAll();
}
注意事项:
- one属性:表示一对一
- column属性:表示用什么名称去匹配property,或用哪个属性作为查询参数
- select属性:表示需要连着查询的方法
- fetchType属性:立即加载或者延迟加载
7.4 多表查询:一对多
例子:一个用户可以有多个账户,每个账户只属于一个用户
实体类,主表实体应该包含从表实体的映射
public class User implements Serializable {
/* 确保属性名称跟数据库一样,mysql数据库在windows上不区分大小写 */
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// 一对多的关系映射,主表实体应该包含从表实体的映射
private List<Account> accounts;
}
如果采用xml配置
<!-- 定义User的resultMap,做一对多的映射 -->
<resultMap id="userAccountMap" type="com.zhq.study.mybatis.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<!-- 配置user对象中的account集合,建立一对多关系 -->
<collection property="accounts" ofType="com.zhq.study.mybatis.domain.Account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
<!-- 配置所有查询 -->
<select id="findAll" resultMap="userAccountMap">
select * from user u left outer join account a on u.id=a.uid;
</select>
也可以通过另一个查询方法
<!-- 定义User的resultMap,做一对多的映射 -->
<resultMap id="userAccountMap" type="com.zhq.study.mybatis.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<!-- 配置user对象中的account集合,建立一对多关系 -->
<collection property="accounts" ofType="com.zhq.study.mybatis.domain.Account"
select="com.zhq.study.mybatis.dao.IAccountDao.findAllByUid" column="id"/>
</resultMap>
如果采用注解方式
public interface IUserDao {
/* 查询所有用户*/
/* 如果实体类的名称和数据库不对应,使用Results注解解决 */
@Select("select * from user")
@Results(id = "userMap", value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "username", property = "username"),
@Result(column = "address", property = "address"),
@Result(column = "sex", property = "sex"),
@Result(column = "birthday", property = "birthday"),
@Result(property = "accounts", column = "id",
many = @Many(select = "com.zhq.study.mybatis.dao.IAccountDao.findAllByUid", fetchType = FetchType.LAZY))
})
List<User> findAll();
}
注意事项:
- many属性:表示一对多
- column属性:表示用什么名称去匹配property,或用哪个属性作为查询参数
- select属性:表示需要连着查询的方法
- fetchType属性:立即加载或者延迟加载
7.5 多表查询:多对多
例子:一个用户可以是多个角色,一个角色也可以属于多个用户
多对多需要一张中间表作为连接
配置同一对多
7.6 延迟加载与立即加载
一般来说,一对多,加载账户的时候需要同时加载用户信息(立即加载),加载用户的时候不必要同时加载账户信息(延迟加载)
延迟加载是利用另一个查询方法,做到在需要结果的时候再查询
主配置文件
<settings>
<!-- 开启mybatis支持延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
一对一
<!-- 定义封装account和user的resultMap -->
<resultMap id="accountUserMap" type="com.zhq.study.mybatis.domain.Account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 建立一对一的关系映射,配置封装user的内容 -->
<!-- 延迟加载,select用于指定的内容:查询用户的唯一标识 -->
<!-- 延迟加载需要在主配置文件配置 -->
<association property="user" column="uid" select="com.zhq.study.mybatis.dao.IUserDao.findById"/>
</resultMap>
多对多
<!-- 定义User的resultMap,做一对多的映射 -->
<resultMap id="userAccountMap" type="com.zhq.study.mybatis.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<!-- 配置user对象中的account集合,建立一对多关系 -->
<!-- 延迟加载,使用select指定加载的内容 -->
<collection property="accounts" ofType="com.zhq.study.mybatis.domain.Account"
select="com.zhq.study.mybatis.dao.IAccountDao.findAllByUid" column="id"/>
</resultMap>
注意事项:
- select属性:指定需要延迟加载调用的查询方法
- column属性:传入延迟加载查询方法的参数字段
- ofType属性:因为实体类属性是集合,需要指定集合元素的类型
7.7 缓存
适用于缓存的数据:经常查询、不经常改变、数据的正确与否对最终结果影响不大
不适用于缓存的数据:经常改变的数据、数据的正确与否对最终结果影响很大
7.7.1 一级缓存
一级缓存是SqlSession对象的缓存,执行查询之后,查询结果会同时存入SqlSession提供的缓存区域中,该区域的结构是一个map,当再次查询同样的数据时,mybatis会首先去SqlSession缓存当中查询
当调用修改、添加、删除、commit()、close()方法时,一级缓存就清空了
mybatis默认开启一级缓存
7.7.2 二级缓存
二级缓存是SqlSessionFactory对象的缓存
主配置文件
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
如果采用xml配置
<cache/>
<select id="findAll" resultMap="userAccountMap" useCache="true">
select * from user;
</select>
如果采用注解方式
@CacheNamespace(blocking = true)
public interface IUserDao {
...
}
8 Mybatis分析
8.1 代理
mybatis中,xml配置或者注解都是使用代理dao的方式实现dao的功能
在代理dao中,调用selectList方法
selectList方法中,根据配置文件注册连接驱动,获取sql语句创建预处理对象PreparedStatement,执行sql语句,使用反射机制将结果封装到对象中
8.2 连接池
连接池是用于存储连接的一个容器,该容器是线程安全的集合,具有先进先出特性
在主配置文件中数据源的type属性,除了POOLED,还可以是UNPOOLED或者JNDI
UNPOOLED:采用传统获取连接的方式,需要的时候创建连接
JNDI:采用服务器提供的JNDI技术实现,不同服务器拿到DataSource是不一样的,必须使用web或者maven的war工程才能使用
9 如何调用运行
// 1. 读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 创建SqlSessionFactory工厂
// 使用了构建者模式,优势:把对象的创建细节隐藏
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3. 使用工厂生产SqlSession对象
// 使用了工厂模式,优势:解耦,降低类之间的依赖关系
SqlSession session = factory.openSession();
// 4. 使用SqlSession创建Dao接口的代理对象
// 使用了代理模式,优势:不修改源码的基础上对已有方法增强
IUserDao userDao = session.getMapper(IUserDao.class);
// 5. 使用代理对象执行方法
List<User> users = userDao.findAll();
for(User user: users){
System.out.println(user);
}
// 6. 提交事务(如果需要的话)
session.commit();
// 7. 释放资源
session.close();
in.close();