Mybatis
Posted liujiashun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis相关的知识,希望对你有一定的参考价值。
Mybatis
第一章
1.1 JDBC编程分析
- 加载驱动
- 获取连接(数据库连接参数)
- 获取预处理statement对象
- 执行查询或更新
- 对结果集操作
缺点:
- 数据库创建、连接频繁释放,影响性能
- sql语句不方便
- 反正很多,我也说不清
1.2 Mybatis快速入门
- 创建 maven 工程
- 添加 Mybatis3.4.5 的坐标
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- 日志依赖 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<!-- xml解析器 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
</dependencies>
- 编写 User 实体类
- 编写持久层接口 IUserDao
- 编写持久层接口的映射文件 IUserDao.xml
- 创建位置:必须和持久层接口在相同的包中
- 名称:必须以持久层接口名称命名文件名,扩展名是.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">
<mapper namespace="com.itheima.dao.IUserDao">
<!--配置查询所有-->
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
</mapper>
- 编写 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">
<!-- mybatis的主配置文件 -->
<configuration>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务的类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基本信息 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"/>
</mappers>
</configuration>
- 编写测试类
基于注解完成
- 在持久层接口中添加注解
public interface IUserDao {
@Select("select * from user")
List<User> findAll();
}
- 修改 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>
<!-- 其他配置一样 -->
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件
如果是用注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名
-->
<mappers>
<mapper class="com.itheima.dao.IUserDao"/>
</mappers>
</configuration>
注意事项:
- 使用注解配置时,请移除 xml 的映射配置
第二章
2.1 自定义Mybatis框架(基于xml实现)
创建Maven工程
引用相关坐标
引用工具类到项目
XMLConfigBuilder:用于解析xml配置文件(从io流中获取配置文件)
静态方法
//将流中的数据库连接信息和Mapper映射信息封装到Configuration类对象中 public static Configuration loadConfiguration(InputStream config); //根据传入路径参数找到IUserDao.xml文件,将里面的信息内容封装到Mapper中,再将Mapper对象添加到Map集合中,此方法供loadConfiguration()方法调用 private static Map<String,Mapper> loadMapperConfiguration(String mapperPath); //通过注解实现上述方法,也供loadConfiguration()方法调用 private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath);
Executor:用于执行SQL语句,并且封装结果集
方法
//根据Mapper对象和连接对象,查询sql语句,将结果返回到List集合中 public <E> List<E> selectList(Mapper mapper, Connection conn); //释放Statement预处理对象和结果集,供上述方法调用 private void release(PreparedStatement pstm,ResultSet rs);
DataSourceUtil:用于获取连接
//根据configuration配置对象得到获得连接 public static Connection getConnection(Configuration cfg);
编写SqlMapConfig.xml:
- 配置数据库连接信息
- 配置映射文件位置(每个dao的配置文件都需要一个映射Mapper)
编写读取文件类Resources
静态方法
//将制定路径的文件装入流中 public static InputStream getResourceAsStream(String filePath);
编写Mapper类:映射文件对象(JavaBean对象)
属性:
//封装Sql语句 private String queryString; //封装sql查询的返回结果集所对应的实体类的全限定名 private String resultType;
编写Configuration配置类(JavaBean对象)
属性:
//封装驱动 private String driver; //封装url private String url; //封装用户名 private String username; //封装密码 private String password; //封装文件映射对象 private Map<String,Mapper> mappers = new HashMap<String,Mapper>();
编写User实体类
- 属性:用户信息
编写持久层接口IUserDao
- 查询信息方法
编写持久层接口IUserDao对应的IUserDao.xml配置文件
- 配置了接口中查询信息方法所对应的sql语句
编写构建者类SqlSessionFactoryBuilder
方法
//根据流信息返回一个sql工厂 public SqlSessionFactory build(InputStream config);
编写SqlSessionFactory接口和实现类DefaultSqlSessionFactory
属性:
//配置对象,构造方法中赋值 private Configuration cfg;
方法:
//返回一个mysql连接 public SqlSession openSession();
编写SQLSession接口和实现类DefaultSqlSession
属性:
//配置类,构造方法中赋值 private Configuration cfg; //真正连接对象,供本类方法调用,构造方法中赋值 private Connection connection;
方法:
//构造方法 public DefaultSqlSession(Configuration cfg); //根据Dao接口字节码创建其代理对象 public <T> T getMapper(Class<T> daoInterfaceClass); //关闭连接 public void close();
编写用于创建Dao接口代理对象的类 MapperProxy(实现了InvocationHandler)
属性:
//map的key是全限定类名+方法名 private Map<String, Mapper> mappers; private Connection conn;
方法:
//构造方法 public MapperProxy(Map<String,Mapper> mappers,Connection conn); //触发代理 public Object invoke(Object proxy, Method method, Object[] args);
运行测试类
public class MybatisTest {
public static void main(String[] args)throws Exception {
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建 SqlSessionFactory 的构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.使用构建者创建工厂对象 SqlSessionFactory
SqlSessionFactory factory = builder.build(in);
//4.使用 SqlSessionFactory 生产 SqlSession 对象
SqlSession session = factory.openSession();
//5.使用 SqlSession 创建 dao 接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//6.使用代理对象执行查询所有方法
List<User> users = userDao.findAll();
for(User user : users) { System.out.println(user);
}
//7.释放资源
session.close();
in.close();
}
}
2.1.1 XMLConfigBuilder类
public class XMLConfigBuilder {
/**
* 解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方
* 使用的技术:
* dom4j+xpath
*/
public static Configuration loadConfiguration(InputStream config){
try{
//定义封装连接信息的配置对象(mybatis的配置对象)
Configuration cfg = new Configuration();
//1.获取SAXReader对象
SAXReader reader = new SAXReader();
//2.根据字节输入流获取Document对象
Document document = reader.read(config);
//3.获取根节点
Element root = document.getRootElement();
//4.使用xpath中选择指定节点的方式,获取所有property节点
List<Element> propertyElements = root.selectNodes("//property");
//5.遍历节点
for(Element propertyElement : propertyElements){
//判断节点是连接数据库的哪部分信息
//取出name属性的值
String name = propertyElement.attributeValue("name");
if("driver".equals(name)){
//表示驱动
//获取property标签value属性的值
String driver = propertyElement.attributeValue("value");
cfg.setDriver(driver);
}
if("url".equals(name)){
//表示连接字符串
//获取property标签value属性的值
String url = propertyElement.attributeValue("value");
cfg.setUrl(url);
}
if("username".equals(name)){
//表示用户名
//获取property标签value属性的值
String username = propertyElement.attributeValue("value");
cfg.setUsername(username);
}
if("password".equals(name)){
//表示密码
//获取property标签value属性的值
String password = propertyElement.attributeValue("value");
cfg.setPassword(password);
}
}
//取出mappers中的所有mapper标签,判断他们使用了resource还是class属性
List<Element> mapperElements = root.selectNodes("//mappers/mapper");
//遍历集合
for(Element mapperElement : mapperElements){
//判断mapperElement使用的是哪个属性
Attribute attribute = mapperElement.attribute("resource");
if(attribute != null){
System.out.println("使用的是XML");
//表示有resource属性,用的是XML
//取出属性的值
String mapperPath = attribute.getValue();//获取属性的值"com/itheima/dao/IUserDao.xml"
//把映射配置文件的内容获取出来,封装成一个map
Map<String,Mapper> mappers = loadMapperConfiguration(mapperPath);
//给configuration中的mappers赋值
cfg.setMappers(mappers);
}else{
System.out.println("使用的是注解");
//表示没有resource属性,用的是注解
//获取class属性的值
String daoClassPath = mapperElement.attributeValue("class");
//根据daoClassPath获取封装的必要信息
Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath);
//给configuration中的mappers赋值
cfg.setMappers(mappers);
}
}
//返回Configuration
return cfg;
}catch(Exception e){
throw new RuntimeException(e);
}finally{
try {
config.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
/**
* 根据传入的参数,解析XML,并且封装到Map中
* @param mapperPath 映射配置文件的位置
* @return map中包含了获取的唯一标识(key是由dao的全限定类名和方法名组成)
* 以及执行所需的必要信息(value是一个Mapper对象,里面存放的是执行的SQL语句和要封装的实体类全限定类名)
*/
private static Map<String,Mapper> loadMapperConfiguration(String mapperPath)throws IOException {
InputStream in = null;
try{
//定义返回值对象
Map<String,Mapper> mappers = new HashMap<String,Mapper>();
//1.根据路径获取字节输入流
in = Resources.getResourceAsStream(mapperPath);
//2.根据字节输入流获取Document对象
SAXReader reader = new SAXReader();
Document document = reader.read(in);
//3.获取根节点
Element root = document.getRootElement();
//4.获取根节点的namespace属性取值
String namespace = root.attributeValue("namespace");//是组成map中key的部分
//5.获取所有的select节点
List<Element> selectElements = root.selectNodes("//select");
//6.遍历select节点集合
for(Element selectElement : selectElements){
//取出id属性的值 组成map中key的部分
String id = selectElement.attributeValue("id");
//取出resultType属性的值 组成map中value的部分
String resultType = selectElement.attributeValue("resultType");
//取出文本内容 组成map中value的部分
String queryString = selectElement.getText();
//创建Key
String key = namespace+"."+id;
//创建Value
Mapper mapper = new Mapper();
mapper.setQueryString(queryString);
mapper.setResultType(resultType);
//把key和value存入mappers中
mappers.put(key,mapper);
}
return mappers;
}catch(Exception e){
throw new RuntimeException(e);
}finally{
in.close();
}
}
/**
* 根据传入的参数,得到dao中所有被select注解标注的方法。
* 根据方法名称和类名,以及方法上注解value属性的值,组成Mapper的必要信息
* @param daoClassPath
* @return
*/
private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{
//定义返回值对象
Map<String,Mapper> mappers = new HashMap<String, Mapper>();
//1.得到dao接口的字节码对象
Class daoClass = Class.forName(daoClassPath);
//2.得到dao接口中的方法数组
Method[] methods = daoClass.getMethods();
//3.遍历Method数组
for(Method method : methods){
//取出每一个方法,判断是否有select注解
boolean isAnnotated = method.isAnnotationPresent(Select.class);
if(isAnnotated){
//创建Mapper对象
Mapper mapper = new Mapper();
//取出注解的value属性值
Select selectAnno = method.getAnnotation(Select.class);
String queryString = selectAnno.value();
mapper.setQueryString(queryString);
//获取当前方法的返回值,还要求必须带有泛型信息
Type type = method.getGenericReturnType();//List<User>
//判断type是不是参数化的类型
if(type instanceof ParameterizedType){
//强转
ParameterizedType ptype = (ParameterizedType)type;
//得到参数化类型中的实际类型参数
Type[] types = ptype.getActualTypeArguments();
//取出第一个
Class domainClass = (Class)types[0];
//获取domainClass的类名
String resultType = domainClass.getName();
//给Mapper赋值
mapper.setResultType(resultType);
}
//组装key的信息
//获取方法的名称
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String key = className+"."+methodName;
//给map赋值
mappers.put(key,mapper);
}
}
return mappers;
}
}
2.1.2 Executor类
public class Executor {
public <E> List<E> selectList(Mapper mapper, Connection conn) {
PreparedStatement pstm = null;
ResultSet rs = null;
try {
//1.取出mapper中的数据
String queryString = mapper.getQueryString();//select * from user
String resultType = mapper.getResultType();//com.itheima.domain.User
Class domainClass = Class.forName(resultType);
//2.获取PreparedStatement对象
pstm = conn.prepareStatement(queryString);
//3.执行SQL语句,获取结果集
rs = pstm.executeQuery();
//4.封装结果集
List<E> list = new ArrayList<E>();//定义返回值
while(rs.next()) {
//实例化要封装的实体类对象
E obj = (E)domainClass.newInstance();
//取出结果集的元信息:ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
//取出总列数
int columnCount = rsmd.getColumnCount();
//遍历总列数
for (int i = 1; i <= columnCount; i++) {
//获取每列的名称,列名的序号是从1开始的
String columnName = rsmd.getColumnName(i);
//根据得到列名,获取每列的值
Object columnValue = rs.getObject(columnName);
//给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装)
PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//要求:实体类的属性和数据库表的列名保持一种
//获取它的写入方法
Method writeMethod = pd.getWriteMethod();
//把获取的列的值,给对象赋值
writeMethod.invoke(obj,columnValue);
}
//把赋好值的对象加入到集合中
list.add(obj);
}
return list;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
release(pstm,rs);
}
}
private void release(PreparedStatement pstm,ResultSet rs){
if(rs != null){
try {
rs.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(pstm != null){
try {
pstm.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
2.1.3 DataSourceUtil类
public class DataSourceUtil {
/**
* 用于获取一个连接
* @param cfg
* @return
*/
public static Connection getConnection(Configuration cfg){
try {
Class.forName(cfg.getDriver());
return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
}catch(Exception e){
throw new RuntimeException(e);
}
}
}
2.1.4 Resources类
public class Resources {
/**
* 根据传入的参数,获取一个字节输入流
* @param filePath
* @return
*/
public static InputStream getResourceAsStream(String filePath){
return Resources.class.getClassLoader().getResourceAsStream(filePath);
}
}
2.1.5 Mapper类
public class Mapper {
private String queryString;//SQL
private String resultType;//实体类的全限定类名
public String getQueryString() {
return queryString;
}
public void setQueryString(String queryString) {
this.queryString = queryString;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
}
2.1.6 Configuration
public class Configuration {
private String driver;
private String url;
private String username;
private String password;
private Map<String,Mapper> mappers = new HashMap<String,Mapper>();
public Map<String, Mapper> getMappers() {
return mappers;
}
public void setMappers(Map<String, Mapper> mappers) {
this.mappers.putAll(mappers);//此处需要使用追加的方式
}
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
2.1.7 SqlSessionFactoryBuilder类
public class SqlSessionFactoryBuilder {
/**
* 根据参数的字节输入流来构建一个SqlSessionFactory工厂
* @param config
* @return
*/
public SqlSessionFactory build(InputStream config){
Configuration cfg = XMLConfigBuilder.loadConfiguration(config);
return new DefaultSqlSessionFactory(cfg);
}
}
2.1.8 DefaultSqlSessionFactory类
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private Configuration cfg;
public DefaultSqlSessionFactory(Configuration cfg){
this.cfg = cfg;
}
/**
* 用于创建一个新的操作数据库对象
* @return
*/
@Override
public SqlSession openSession() {
return new DefaultSqlSession(cfg);
}
}
2.1.9 DefaultSqlSession类
public class DefaultSqlSession implements SqlSession {
private Configuration cfg;
private Connection connection;
public DefaultSqlSession(Configuration cfg){
this.cfg = cfg;
connection = DataSourceUtil.getConnection(cfg);
}
/**
* 用于创建代理对象
* @param daoInterfaceClass dao的接口字节码
* @param <T>
* @return
*/
@Override
public <T> T getMapper(Class<T> daoInterfaceClass) {
return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(),connection));
}
/**
* 用于释放资源
*/
@Override
public void close() {
if(connection != null) {
try {
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2.1.10 MapperProxy类
public class MapperProxy implements InvocationHandler {
//map的key是全限定类名+方法名
private Map<String, Mapper> mappers;
private Connection conn;
public MapperProxy(Map<String,Mapper> mappers,Connection conn){
this.mappers = mappers;
this.conn = conn;
}
/**
* 用于对方法进行增强的,我们的增强其实就是调用selectList方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1.获取方法名
String methodName = method.getName();
//2.获取方法所在类的名称
String className = method.getDeclaringClass().getName();
//3.组合key
String key = className+"."+methodName;
//4.获取mappers中的Mapper对象
Mapper mapper = mappers.get(key);
//5.判断是否有mapper
if(mapper == null){
throw new IllegalArgumentException("传入的参数有误");
}
//6.调用工具类执行查询所有
return new Executor().selectList(mapper,conn);
}
}
2.2 基于注解自定义Mybatis框架
自定义@Select注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Select { /** * 配置SQL语句的 * @return */ String value(); }
修改持久层接口
public interface IUserDao { /** * 查询所有操作 * @return */ @Select("select * from user") List<User> findAll(); }
修改SqlMapConfig.xml
<!-- 告知 mybatis 映射配置的位置 --> <mappers> <mapper class="com.itheima.dao.IUserDao"/> </mappers>
第三章
3.1 基于代理Dao实现CRUD操作
在持久层接口中添加方法
User findById(Integer userId); int saveUser(User user); //返回值是影响数据库记录的行数 List<User> findByName(String username); //模糊查询
在用户的映射配置文件中配置
<select id="findById" resultType="com.itheima.domain.User" parameterType="int"> select * from user where id = #{uid} </select> <insert id="saveUser" parameterType="com.itheima.domain.User"> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert> <!-- 保存用户,上述方法的升级--> <insert id="saveUser" parameterType="user"> <!-- 配置插入操作后,获取插入数据的id --> <selectKey keyProperty="userId" keyColumn="id" resultType="int" order="AFTER"> select last_insert_id(); </selectKey> insert into user(username,address,sex,birthday) values(#{userName},#{userAddress},#{userSex},#{userBirthday}); </insert> <!-- 模糊查询方式一 --> <select id="findByName" resultType="com.itheima.domain.User" parameterType="String"> select * from user where username like #{username} </select> <!-- 模糊查询方式二 --> <select id="findByName" resultType="com.itheima.domain.User" parameterType="string"> select * from user where username like '%${value}%' </select> <select id="findTotal" resultType="int"> select count(*) from user; </select> #{}表示一个占位符号 通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换, #{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类 型值,#{}括号中可以是 value 或其它名称。 ${}表示拼接 sql 串 通过${}可以将 parameterType 传入的内容拼接在 sql中且不进行 jdbc 类型转换, ${}可以接收简 单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value #{}中内容的写法: 若数据类型是基本类型,可以随意写。
在测试类添加测试
public class MybatisTest { private InputStream in; private SqlSession sqlSession; private IUserDao userDao; @Before//用于在测试方法执行之前执行 public void init()throws Exception{ //1.读取配置文件,生成字节输入流 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.获取SqlSessionFactory SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //3.获取SqlSession对象 sqlSession = factory.openSession(); //4.获取dao的代理对象 userDao = sqlSession.getMapper(IUserDao.class); } @After//用于在测试方法执行之后执行 public void destroy()throws Exception{ //提交事务,Mybatis中默认是不自动提交事务 sqlSession.commit(); //6.释放资源 sqlSession.close(); in.close(); } /** * 测试查询一个 */ @Test public void testFindOne(){ //5.执行查询一个方法 User user = userDao.findById(50); System.out.println(user); } /** * 测试模糊查询操作 */ @Test public void testFindByName(){ //5.执行查询一个方法 List<User> users = userDao.findByName("%王%"); // List<User> users = userDao.findByName("王"); for(User user : users){ System.out.println(user); } } /** * 测试查询总记录条数 */ @Test public void testFindTotal(){ //5.执行查询一个方法 int count = userDao.findTotal(); System.out.println(count); } /** * 测试使用QueryVo作为查询条件 */ @Test public void testFindByVo(){ QueryVo vo = new QueryVo(); User user = new User(); user.setUserName("%王%"); vo.setUser(user); //5.执行查询一个方法 List<User> users = userDao.findUserByVo(vo); for(User u : users){ System.out.println(u); } } }
3.2 Mybatis的参数深入
parameterType 配置参数
传递 pojo 包装对象
需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。
编写QueryVo
public class QueryVo implements Serializable { private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
编写持久层接口
List<User> findByVo(QueryVo vo);
持久层接口的映射文件
<select id="findByVo" resultType="com.itheima.domain.User" parameterType="com.itheima.domain.QueryVo"> select * from user where username like #{user.username}; </select>
3.3 Mybatis的输出结果封装
- resultType
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
<!-- 查询的结果集中的名称要与结果封装实体类中的属性名相对应,如果不对应,则很可能封装不上去,怎么办 -->
<!-- 可以通过起别名的方法使得结果集中的名称要与结果封装实体类中的属性名相对应 -->
<select id="findAll" resultType="com.itheima.domain.User">
select id as userId,username as userName,birthday as userBirthday, sex as userSex,address as userAddress from user
</select>
- resultMap
<!-- 通过resultMap直接设定对应规则 -->
<resultMap type="com.itheima.domain.User" id="userMap">
<id column="id" property="userId"/>
<result column="username" property="userName"/>
<result column="sex" property="userSex"/>
<result column="address" property="userAddress"/>
<result column="birthday" property="userBirthday"/>
</resultMap>
<select id="findAll" resultMap="userMap">
select * from user
</select>
3.4 SqlMapConfig.xml配置文件
- properties(属性)
- property
- settings(全局配置参数)
- setting
- typeAliases (类型别名)
- typeAliase
- package
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- environments (环境集合属性对象)
- environment
- mappers(映射器)
- mapper
- package
3.4.1 如何配置
<!-- properties配置方式一,直接在配置文件中配置 -->
<properties>
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="jdbc.url" value="jdbc:mysql://localhost:3306/eesy"/>
<property name="jdbc.username" value="root"/> <property name="jdbc.password" value="1234"/>
</properties>
<!-- properties配置方式二,在 classpath 下定义 db.properties 文件 ,然后引入 -->
<properties resource="jdbcConfig.properties" >
</properties>
<!--配置环境-->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池,可以引入properties中配置的resource-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="com.itheima.domain.User"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name="com.itheima.domain"/>
<package name=" 其它包 "/>
</typeAliases>
<mappers>
<!-- 使用相对于类路径的资源注册 -->
<mapper resource="com/itheima/dao/IUserDao.xml"></mapper>
<!-- 使用 mapper 接口类路径注册 -->
<mapper class="com.itheima.dao.UserDao"/>
<!-- 注册指定包下的所有 mapper 接口 -->
<package name="com.itheima.dao"></package>
</mappers>
第四章
4.1 Mybatis连接池
Mybatis连接池的分类
- UNPOOLED:不使用连接池的数据源
- POOLED:使用连接池的数据源
- JNDI:使用JNDI实现的数据源
- MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的 UnpooledDataSource, PooledDataSource 类来表示 UNPOOLED、POOLED 类型的数据源
在SqlMapConfig.xmlzhong 配置数据源(连接池)信息
<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> <!-- type=”POOLED”:MyBatis 会创建 PooledDataSource 实例 type=”UNPOOLED” : MyBatis 会创建 UnpooledDataSource 实例 type=”JNDI”:MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用 -->
Mybatis 中 DataSource 的存取
- 在Mybatis中通过工厂模式创建数据源对象
4.2 Mybatis的事务控制
Mybatis中事务的提交默认是不自动提交
如果想改成自动提交语法为在openSession方法中加入参数为true
session = factory.openSession(true);
4.3 Mybatis的动态SQL语句
<!-- if标签-->
<select id="findByUser" resultType="user" parameterType="user">
select * from user where 1=1
<if test="username!=null and username != '' ">
and username like #{username}
</if>
<if test="address != null">
and address like #{address}
</if>
</select>
<!-- 其中test中的username是参数user对象中的对应username属性名,-->
<!-- 抽取重复的sql语句-->
<sql id="defaultUser">
select * from user
</sql>
<!-- where标签-->
<select id="findByUser" resultType="user" parameterType="user">
<!-- 引用代码片段-->
<include refid="defaultSql"></include>
<where>
<if test="username!=null and username != '' ">
and username like #{username}
</if>
<if test="address != null">
and address like #{address}
</if>
</where>
</select>
<!-- 查询所有用户在 id 的集合之中,其中参数对象为queryvo,里面聚合了一个list<Interger> ids对象,存放一组id值 -->
<select id="findInIds" resultType="user" parameterType="queryvo">
<!-- select * from user where id in (1,2,3,4,5); -->
<include refid="defaultSql"></include>
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open="id in ( " close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
4.4 多表查询
4.1.1 一对一查询
方式一
定义账户信息实体类Account,和用户User类
public class Account implements Serializable { private Integer id; private Integer uid; private Double money; } public class User implements Serializable{ private Integer id; private String username; private Date birthday; private String sex; private String address; }
编写sql语句
定义AccountUser类,继承Acount类,包含用户信息相关属性(相当于中间桥梁,连接Account与User类)
public class AccountUser extends Account implements Serializable { private String username; private String address; }
定义账户的持久层Dao接口查询返回封装的类型为AccountUser
定义AccountDao.xml的配置信息
<!-- 抽取重复的sql语句--> <mapper namespace="com.itheima.dao.IAccountDao"> <!-- 配置查询所有操作--> <select id="findAll" resultType="accountuser"> select a.*,u.username,u.address from account a,user u where a.uid =u.id; </select> </mapper>
方式二
定义一个Account类(由继承改为了引用)
public class Account implements Serializable { private Integer id; private Integer uid; private Double money; //从表实体应该包含一个主表实体的对象引用 private User user; }
修改AccountDao接口中的方法(返回值封装到Account对象中)
重新定义AccountDao.xml文件
<mapper namespace="com.itheima.dao.IAccountDao"> <!-- 建立对应关系 --> <resultMap type="account" id="accountMap"> <id column="aid" property="id"/> <result column="uid" property="uid"/> <result column="money" property="money"/> <!-- 它是用于指定从表方的引用实体属性的 --> <association property="user" javaType="user"> <id column="id" property="id"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="birthday" property="birthday"/> <result column="address" property="address"/> </association> </resultMap> <select id="findAll" resultMap="accountMap"> select u.*,a.id as aid,a.uid,a.money from account a,user u where a.uid =u.id; </select> </mapper>
编写测试类
4.1.2 一对多查询
在User类中加入List
对象 在用户持久层Dao接口中加入查询方法
用户持久层Dao映射文件配置
<mapper namespace="com.itheima.dao.IUserDao"> <!-- 定义User的resultMap--> <resultMap id="userAccountMap" type="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对象中accounts集合的映射 --> <collection property="accounts" ofType="account"> <id column="aid" property="id"></id> <result column="uid" property="uid"></result> <result column="money" property="money"></result> </collection> </resultMap> <!-- 查询所有 --> <select id="findAll" resultMap="userAccountMap"> select * from user u left outer join account a on u.id = a.uid </select> <!-- 根据id查询用户 --> <select id="findById" parameterType="INT" resultType="user"> select * from user where id = #{uid} </select> </mapper>
编写测试方法
4.1.3 多对多
角色到用户的多对多
建立多对多关系模型
- 在数据库中创建 用户表,用户角色表(中间表),角色表
业务要求和实现Sql语句
编写实体类
public class Role implements Serializable { private Integer roleId; private String roleName; private String roleDesc; //多对多的关系映射:一个角色可以赋予多个用户 private List<User> users; } public class User implements Serializable { private Integer id; private String username; private String address; private String sex; private Date birthday; //多对多的关系映射:一个用户可以具备多个角色 private List<Role> roles; }
编写Role持久层接口
public interface IRoleDao { List<Role> findAll(); }
编写映射文件IRoleDao.xml
<mapper namespace="com.itheima.dao.IRoleDao"> <!--定义role表的ResultMap--> <resultMap id="roleMap" type="role"> <id property="roleId" column="rid"></id> <result property="roleName" column="role_name"></result> <result property="roleDesc" column="role_desc"></result> <collection property="users" ofType="user"> <id column="id" property="id"></id> <result column="username" property="username"></result> <result column="address" property="address"></result> <result column="sex" property="sex"></result> <result column="birthday" property="birthday"></result> </collection> </resultMap> <!--查询所有--> <select id="findAll" resultMap="roleMap"> select u.*,r.id as rid,r.role_name,r.role_desc from role r left outer join user_role ur on r.id = ur.rid left outer join user u on u.id = ur.uid </select> </mapper>
编写测试类
用户到角色的多对多(原理还是基于一对多)
同上
第五章
5.1 Mybatis延迟加载策略
使用association实现延迟加载
开启延迟加载的功能(在SqlMapConfig.xml中配置)
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
编写Dao层
public interface IAccountDao { List<Account> findAll(); }
编写持久层映射
<!-- 定义封装account和user的resultMap --> <resultMap id="accountUserMap" type="account"> <id property="id" column="id"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <!-- 一对一的关系映射:配置封装user的内容 select属性指定的内容:查询用户的唯一标识: column属性指定的内容:用户根据id查询时,所需要的参数的值 --> <association property="user" column="uid" javaType="user" select="com.itheima.dao.IUserDao.findById"></association> </resultMap> <!-- 查询所有 --> <select id="findAll" resultMap="accountUserMap"> select * from account </select> <!-- 根据用户id查询账户列表 --> <select id="findAccountByUid" resultType="account"> select * from account where uid = #{uid} </select> </mapper>
编写测试类
@Test public void testFindAll() { //6.执行操作 List<Account> accounts = accountDao.findAll(); } //执行完后只会查询账户基本信息,其关联的用户信息未加载(未查询),当你需要遍历这个accounts时,才会加载用户信息
使用Collection实现延迟加载
编写用户持久层映射配置
<mapper namespace="com.itheima.dao.IUserDao"> <!-- 定义User的resultMap--> <resultMap id="userAccountMap" type="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对象中accounts集合的映射 --> <collection property="accounts" ofType="account" select="com.itheima.dao.IAccountDao.findAccountByUid" column="id"></collection> </resultMap> <!-- 查询所有 --> <select id="findAll" resultMap="userAccountMap"> select * from user </select> <!-- 根据id查询用户 --> <select id="findById" parameterType="INT" resultType="user"> select * from user where id = #{uid} </select> </mapper>
5.2 Mybatis缓存
一级缓存
- 是 SqlSession 范围的缓存(如连续调用两次相同条件的查询,返回的查询结果对象是同一个对象),但当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
二级缓存
是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的
二级缓存的开启
在SqlMapConfig.xml配置
<settings> <!-- 开启二级缓存的支持 --> <setting name="cacheEnabled" value="true"/> </settings>
配置相关的Mapper映射文件
<mapper namespace="com.itheima.dao.IUserDao"> <!--开启user支持二级缓存--> <cache/> <!-- useCache=”true”代表当前这个 statement 要使用 二级缓存,如果不使用二级缓存可以设置为 false--> <select id="findById" resultType="user" parameterType="int" useCache="true"> select * from user where id = #{uid} </select> </mapper>
二级缓存注意事项
- 当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化 方式来保存对象。
5.3 Mybatis的常用注解说明
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
5.4 使用Mybatis注解实现基本CRUD
编写实体类
使用注解方式开发持久层接口
public interface IUserDao { /** * 查询所有用户 * @return */ @Select("select * from user") @Results(id="userMap",value={ @Result(id=true,column = "id",property = "userId"), @Result(column = "username",property = "userName"), @Result(column = "address",property = "userAddress"), @Result(column = "sex",property = "userSex"), @Result(column = "birthday",property = "userBirthday"), @Result(property = "accounts",column = "id", many = @Many(select = "com.itheima.dao.IAccountDao.findAccountByUid", fetchType = FetchType.LAZY)) }) List<User> findAll(); /** * 保存用户 * @param user */ @Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})") void saveUser(User user); /** * 更新用户 * @param user */ @Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}") void updateUser(User user); /** * 删除用户 * @param userId */ @Delete("delete from user where id=#{id} ") void deleteUser(Integer userId); /** * 根据id查询用户 * @param userId * @return */ @Select("select * from user where id=#{id} ") @ResultMap("userMap") User findById(Integer userId); /** * 根据用户名称模糊查询 * @param username * @return */ // @Select("select * from user where username like '%${value}%' ") @Select("select * from user where username like #{username} ") @ResultMap("userMap") List<User> findUserByName(String username); /** * 查询总用户数量 * @return */ @Select("select count(*) from user ") int findTotalUser(); } public interface IAccountDao { /** * 查询所有账户,并且获取每个账户所属的用户信息 * @return */ @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.itheima.dao.IUserDao.findById",fetchType= FetchType.EAGER)) }) List<Account> findAll(); /** * 根据用户id查询账户信息 * @param userId * @return */ @Select("select * from account where uid = #{userId}") List<Account> findAccountByUid(Integer userId); }
编写 SqlMapConfig 配置文件
编写测试方法
5.5 复杂关系映射的注解说明
@Results 注解
代替的是标签<resultMap> 该注解中可以使用单个@Result 注解,也可以使用@Result 集合 @Results({@Result(),@Result()})或@Results(@Result())
@Result
属性介绍:
id 是否是主键字段
column 数据库的列名
property 需要装配的属性名
one 需要使用的@One 注解(@Result(one=@One)()))
many 需要使用的@Many 注解(@Result(many=@many)()))
@One 注解(一对一)
代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One 注解属性介绍:
select 指定用来多表查询的sqlmapper
fetchType 会覆盖全局的配置参数lazyLoadingEnabled。。
使用格式: @Result(column=" ",property="",one=@One(select=""))
@Many 注解(多对一)
代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。
注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType (一般为 ArrayList)但是注解中可以不定义;
使用格式: @Result(property="",column="",many=@Many(select=""))
5.6 mybatis基于注解的二级缓存
在SqlMapConfig 中开启二级缓存支持
<settings> <!-- 开启二级缓存的支持 --> <setting name="cacheEnabled" value="true"/> </settings>
在持久层接口中使用注解配置二级缓存
@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存 public interface IUserDao {}
以上是关于Mybatis的主要内容,如果未能解决你的问题,请参考以下文章
SSM-MyBatis-05:Mybatis中别名,sql片段和模糊查询加getMapper
MYBATIS05_ifwherechoosewhentrimsetforEach标签sql片段