MyBatis
一:简介:
作用: 数据访问层框架.
底层是对 JDBC 的封装.
mybatis :优点之一:
使用 mybatis 时不需要编写实现类,只需要写需要执行的 sql 命
二:环境搭建(这里创建的是web项目)
注意:这里并没有写对应的显示类,这就是Mybatis的有点之一,他通过反射的方式,将对应的mapper.xml文件动态的生成实现类,来执行了;
1)创建maven项目,目录结构
2)pom.xml
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> </build>
3)mybatis-conf.xml
(这个文件跟spring无关,因为这时并没有一如spring但却要创建mybatis全局配置文件,我这里配了一个模板(也可以到mybatis文档中复制一份),mybatis-conf-xml,注意修改mapper.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--> <properties resource="db.properties"/> <!--取别名--> <typeAliases> <!--这种方式只能一个一个类的起别名,太麻烦--> <!--<typeAlias type="com.xpl.model.User" alias="User"/>--> <!--这种方式可以指定给某个包下的所有类起别名,并且可以写多个package--> <package name="com.xpl.model"/> </typeAliases> <!--default随意起名字,value是下边environment的id意思就是当前使用的数据源是什么--> <environments default="development"> <!--environment可以配置多个数据源--> <environment id="development"> <!-- 使用原生JDBC事务 --> <transactionManager type="JDBC"/> <!--type中pooled是数据连接池--> <!--<dataSource type="POOLED">--> <!--<property name="driver" value="com.mysql.jdbc.Driver"/>--> <!--<property name="url" value="jdbc:mysql:///test"/>--> <!--<property name="username" value="root"/>--> <!--<property name="password" value="root"/>--> <!--</dataSource>--> <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> <!--配置mapper.xml文件到mybatis中--> <mappers> <!--resource是mapper.xml的全文件名(区别于全路径名,全路径名中间是".",这里是"/"),适用于,多个mapper.xml文件一个一个的精准的写入--> <mapper resource="com/xpl/mapper/UserMapper.xml"/> <!--这种方式用来和接口式编程的方式配合,扫描mapper接口所在德包--> <!--<package name="com.xpl.mapper"/>--> </mappers> </configuration>
4)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是命名空间,相当于是取一个唯一的名字,用于区分,建议使用对应的类的全路径名--> <mapper namespace="com.xpl.pojo.User"> <!--result是返回值类型,因为mybatis是一行一行的读,所以读一行就会封装一个对应的对象,所以返回值类型的对应的类--> <select id="selectUser" resultType="com.xpl.pojo.User"> select * from USER ; </select> </mapper>
5)Test.java
public class Test { public static void main(String[] args) throws IOException { // 通过流加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml"); // 通过构建模式来创建SqlsessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 打开session SqlSession sqlSession = sqlSessionFactory.openSession(); // 执行sql List<User> users = sqlSession.selectList("com.xpl.model.User.selAllUser"); System.out.println(users); // 记得提交和关闭 sqlSession.commit(); sqlSession.close(); } }
6)日志文件(加上这个文件并在pom中引入日志依赖:作用,运行的时候我们可以在控制台上看到生成的sql)
我这里的log4j文件是制作的模板,一键生成的
具体的日志的详细,后期会写
log4j.rootLogger=DEBUG,stdout
log4j.logger.org.mybatis=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p %d %C: %m%n
7)db.properties
db.driver=com.mysql.jdbc.Driver db.username=root db.password=root db.url=jdbc:mysql:///test?useUnicode=true&characterEncoding=UTF-8
三:分析,理解:
这里主要的文件有四个:
一:pom.xml
因为我们这里虽然创建的是web项目,但是我们使用main方法启动的,并没有使用web容器启动;
所以只需要加mybatis和数据库驱动的依赖
因为我们把配置文件写在了resource中Idea是默认在src中加载(classpath:路径下)
所以我们要配置buil属性将默认路径进行更改
二:mapper.xml文件
这个文件的作用就是给程序员写sql的
因为mybatis的优点之一就是不需要实现类,会根据mapper.xml文件通过反射机制动态创建实现,
所以我们只写mapper就可以了
因为namespace定义了命名空间确定唯一性,所以名字可以随意,只要不重复
但是建议使用对应类的全路径名
三:mybatis.xml文件
配置的属性有:
数据源:
事务类型
数据源配置
mapper的引入
四:Test.java
使用构建模式通过SqlSessionFactory加载mybatis.xml文件并创建SqlSession,并开启session
session通过mapper中的命名空间+id来操作数据库
四:全局配置文件属性解析:
1<transactionManager/> type 属性可取值
<environments default="development"> <!--environment可以配置多个数据源--> <environment id="development"> <!-- 使用原生JDBC事务 --> <transactionManager type="JDBC"/> <!--type中pooled是数据连接池--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///test"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment>
1.1 JDBC,事务管理使用 JDBC 原生事务管理方式
1.2 MANAGED 把事务管理转交给其他容器.原生 JDBC 事务
底层实现:setAutoMapping(false); 就是关闭自动提交,事务交给谁谁来处理;
1.2 <dataSouce/>type 属性
<dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///test"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource>
1.2.1 POOLED 使用数据库连接池
1.2.2 UNPOOLED 不实用数据库连接池,和直接使用 JDBC 一样
1.2.3 JNDI :java命名和目录技术调用其他技术时使用
五:三种查询方式:
selectList(返回值是List集合)
<mapper namespace="com.xpl.model.User"> <select id="selectUser" resultType="com.xpl.pojo.User"> select * from USER ; </select> </mapper> List<User> users = sqlSession.selectList("com.xpl.pojo.User.selectUser"); 输出: User{userName='xxx', address='xxx', id=1} User{userName='xxx', address='xxx', id=2} User{userName='xxx', address='xxx', id=3} User{userName='xxx', address='xxx', id=4}
selectOne(返回值是一个变量或者对象的时候)
<mapper namespace="com.xpl.model.User"> <select id="selectOne" resultType="String"> select userName from USER where id=1; </select> </mapper> <mapper namespace="com.xpl.model.User"> <select id="selOne" resultType="com.xpl.model.User"> select * from user where id=1; ; </select> </mapper>
selectMap(返回值是一个Map)
(Map<Integer, User> id = sqlSession.selectMap("com.xpl.pojo.User.selectMap", "id");
中的“id”将会封装成结果集返回的map集合中的key:将数据库的那个列的值作为key
)
mapper.xml
<select id="selectMap" resultType="com.xpl.pojo.User"> select * from user </select>
main.java
Map<Integer, User> id = sqlSession.selectMap("com.xpl.pojo.User.selectMap", "id"); 输出: {1=User{userName='xxx', address='xxx', id=1}, 2=User{userName='xxx', address='xxx', id=2}, 3=User{userName='xxx', address='xxx', id=3}, 4=User{userName='xxx', address='xxx', id=4}}
六:参数的传递(对五的补充)
传递单个基本数据类型
<!--parameterType是指传入的参数类型-->
//通过parameteType设置传入值的类型,可以取到传入的基本类型的值 <select id="selOne" resultType="com.xpl.pojo.User" parameterType="int"> select * from USER where id=#{0}; </select>
// 通过传递一个基本数据类型的数据进行查询 User user1 = sqlSession.selectOne("com.xpl.pojo.User.selOne", 1); System.out.println(user1); 输出结果: User{userName='xxx', address='xxx', id=1}
传递多个基本数据类型
<!--多个参数需要传入一个封装了多个参数的map-->
//注意!!!:传入的map集合名字可以随便起,但是paramete中的类型必须是map或者是map的全路径名(因为paramete表示的是传入的参数类型)
//通过parametaType设置传入的是一个map:map中封装了传进来的多个参数,取值的时候一定要注意#{id}中的key是传入的map中的key!
<select id="selMap" resultType="com.xpl.pojo.User" parameterType="map"> select * from user where id=#{id} and userName=#{userName}; </select>
//通过传递一个map进行多参数的查询 //将要传的值封装到map中,key是字段名 value是要传的值 Map<String,Object> page=new HashMap<String,Object>(); page.put("id",2); page.put("userName","xxx"); User user2 = sqlSession.selectOne("com.xpl.pojo.User.selMap", page); System.out.println(user2);
输出结果:
User{userName='xxx', address='xxx', id=2}
传递对象
通过paremater设置传入的类型时一个对象,这个对象中的属性值就是我们要去取得值,这个对象中可能之封装了个别参数,不需要管,只用去取我们需要的参数
<select id="selObj" resultType="com.xpl.pojo.User" parameterType="com.xpl.pojo.User"> select * from USER where id=#{id} </select>
//创建一个对象,然后将对象传递过去 //通过传递一个对象进行传值 User user3 =new User(); user3.setId(3); User user4 = sqlSession.selectOne("com.xpl.pojo.User.selObj", user3); System.out.println(user4);
输出结果:
User{userName='xxx', address='xxx', id=3}
传递包装类型(这样插入的值还是user,而不是userW):
<mapper namespace="com.xpl.model.UserW"> <insert id="addUser" parameterType="UserW"> insert into user set userName=#{user.userName},address=#{user.address},id=#{user.id},tid=#{user.tid}; </insert> </mapper>
public class UserW { private User user;
public class User { private int id; private String userName; private String address; private int tid;
七:$和#的区别:
#{}:
使用parameteStatement进行占位符操作,通过日志发现sql中是使用?进行占位,然后将传入的参数进行填充
${}:
使用Statement的方式,进行字符串拼接,不从传入的参数中将其填充,而是使用get/set方法获取的值,
${内容}中的“内容”直接进行字符串拼接,
${数字}如果内容是数字,直接填数字
我们常规使用#{}的方式进行,但是当遇到有模糊匹配like的时候还是要去使用${}来进行
八:setting设置
1.在 mybatis 全局配置文件中通过<settings>标签控制 mybatis 全局开关
九:分页查询:
Map<String,Integer> map=new HashMap<String, Integer>(); // 查询第几页 int pageNumber=1; // 一页显示多少条数据 int pageSize=3; // 从第几个开始查询,也就是将从第几页开始查转换为数据库中limit中的第一个参数 // Mapper.xml文件中取参数时,#{}不支持运算,所以要在这里将其算出来, // limit中的两个参数表示:1.从哪一行开始,2.查询几条。 int pageStart=(pageNumber-1)*pageSize; map.put("pageSize",pageSize); map.put("pageStart",pageStart); List<User> users = sqlSession.selectList("com.xpl.pojo.User.selPage", map); for(User user1:users){ System.out.println(user1); }
<select id="selPage" resultType="com.xpl.pojo.User" parameterType="map"> select * from user limit #{pageStart},#{pageSize}; </select>
十:别名
方式一:
<!--给某个类起个别名--> <typeAliases> <typeAlias type="com.xpl.pojo.User" alias="user"/> </typeAliases>
方式二:
<typeAliases>
<!--给某个包下所有的实体类都起个别名-->
<package name="com.xpl.pojo"/>
</typeAliases>
注意:使用package的方式是默认别名是类名(首字母要大写)
这个时候我们就不用写全类名了
起别名之前的写法: <select id="selObj" resultType="com.xpl.pojo.User" parameterType="com.xpl.pojo.User"> select * from USER where id=#{id} </select> 起别名之后的写法: <select id="selObj" resultType="user" parameterType="user"> select * from USER where id=#{id} </select>
十一:增,删,改(没有也不用写resultType属性)
增:
<insert id="addBook1" parameterType="com.itbaizhan.bean.Book"> insert into book (name,author) values (#{name},#{author}) </insert>
public void test2(){ Book book=new Book(); book.setAuthor("曹雪芹"); book.setName("红楼梦"); int insert = sqlSession.insert("book.addBook1", book); System.out.println(insert); }
删:
<delete id="deleteUser" parameterType="java.lang.String"> delete from user where username=#{username}; </delete>
public void test4(){ Book book=new Book(); book.setName("三国演义"); book.setId(1l); int delete = sqlSession.delete("com.xpl.User.deleteUser",user); System.out.println(delete ); }
改:
<update id="upBookById" parameterType="com.itbaizhan.bean.Book"> update book set name=#{name} where id=#{id} </update>
public void test4(){ Book book=new Book(); book.setName("水浒传"); book.setId(1l); int upBookById = sqlSession.update("upBookById", book); System.out.println(upBookById); }
十二:事务
首先明白三个概念:
功能:从程序角度触发,需要完成的,比如转账就是一个功能:用户A给用户B转钱
业务:从代码角度出发,增曲实现功能的过程对应一个service的方法:比如转账的业务先在A账户减去500块钱,再在B账户增加500块钱;
事务:是指完成一个功能的方法中所执行的sql的集合;比如转账过程先执行A账户减去500的sql语句,在执行B账户增加500块钱的sql语句,这两条sql就是一个事务管理;
mybatis是对jdbc的封装,默认是不会自定提交事务的(关闭了自动提交功能)
在openSession()时Mybatis会创建sqlSession,同时创建一个Transaction(事务对象),
每一个 SqlSession 默认都是不自动提交事务.那么就需要通过session.commit()提交事务.或者通过设置为自动提交事务openSession(true);底层相当于是setAutoCommit(true);
public class Test { public static void main(String[] args) throws IOException { // 通过流加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml"); // 通过构建模式来创建SqlsessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 打开session SqlSession sqlSession = sqlSessionFactory.openSession(true); } }
回滚:
因为可能执行了多条sql但是某一条会出现异常,那么提交之后就会出现数据错乱的情况
解决的办法就是将所有的代码都用try-cath包裹起来,然后在catch中执行rollback这样程序可以正常运行,并且数据库数据是正常的
事例:(这里只是做一个示例演示一下事务和回滚,理论上在service中是不建议try-catch的,后边有方法处理)
User user = new User(); user.setId(12); user.setAddress("周口"); user.setUserName("高蒙"); try { int res = sqlSession.update("com.xpl.pojo.User.insertUser", user); System.out.println(res); }catch (Exception e){ sqlSession.rollback(); } // mybatis默认关闭了自动提交所以这里要提交一下事务 sqlSession.commit();
十三:接口绑定方案及多参数传递(以后常用的方式)
分析:
1.创建一个和mapper.xml文件名称一样的接口,底层通过动态代理实现了这个接口,并且接口中的方法和mapper.xml中的id一致
2.mybatis全局文件中更改配置mapper.xml的方式,使用package的方式定义mapper存在的位置
3.调用的时候,先通过sqlSession加载mapper接口,获取session,通过session来执行接口中的方法;
<?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> <!--给某个类起个别名--> <typeAliases> <!--<typeAlias type="com.xpl.pojo.User" alias="user"/>--> <!--给某个包下所有的实体类都起个别名--> <package name="com.xpl.pojo"/> </typeAliases> <!--default随意起名字,value是下边environment的id意思就是当前使用的数据源是什么--> <environments default="development"> <!--environment可以配置多个数据源--> <environment id="development"> <!-- 使用原生JDBC事务 --> <transactionManager type="JDBC"/> <!--type中pooled是数据连接池--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///test"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--配置mapper.xml文件到mybatis中--> <mappers> <!--resource是mapper.xml的全文件名(区别于全路径名,全路径名中间是".",这里是"/")--> <!--<mapper resource="com/xpl/mapper/UserMapper.xml"/>-->
<!--这种方式只能用在接口式编程,因为他扫的是mapper.xml--> <package name="com.xpl.mapper"/> </mappers> </configuration>
public interface UserMapper { List<User> selAllUser(int id,String userName); }
<?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"> <!--这里的namesqace必须和mapper接口的全路径名一致 id必须和接口中的方法一致--> <mapper namespace="com.xpl.mapper.UserMapper"> <select id="selAllUser" resultType="User"> select * from user where id=#{0} and userName=#{1} ; </select> </mapper>
public class Test { public static void main(String[] args) throws IOException { InputStream is =Resources.getResourceAsStream("mybatis-conf.xml"); // 使用构建者设计模式 // 原因是,SqlSessionFactory的构造方法中使用了configuration,太麻烦直接一步到位 SqlSessionFactory build = new SqlSessionFactoryBuilder().build(is); // 开启session SqlSession sqlSession = build.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.selAllUser(1, "徐沛蕾"); for (User u:users) { System.out.println(u); } } }
传值问题:(解决了传值不方便问题)
因为在mapper.xml中只能通过下表(0.1.2...或者param1.param2....来获取比较麻烦解决办法为)
在接口中定义传入的参数
public interface UserMapper { List<User> selAllUser(@Param("id") int id,@Param("userName") String userName); }
那么在mapper中就可以直接通过参数名来获取了
<mapper namespace="com.xpl.mapper.UserMapper"> <select id="selAllUser" resultType="User"> select * from user where id=#{id} and userName=#{userName} ; </select> </mapper>
注意:
1.接口中@param中的参数名跟后边的id和userName没有关系,他和mapper.xml文件中取值时的{}里边的名字是一致的(有点起个别名的意思)
2.mapper.xml标签中不用写参数类型parameterType
十四:动态sql
if:
<mapper namespace="com.xpl.mapper.UserMapper"> <select id="selAllUser" resultType="User"> select * from user where 1=1 <if test="id !=null and id !=''"> and id=#{id} </if> <if test="userName !=null and userName != ''"> and userName=#{userName} ; </if> </select> </mapper>
where:
功能:
1.去掉and
2.自动生成或者不给where
<mapper namespace="com.xpl.mapper.UserMapper"> <select id="selAllUser" resultType="User"> select * from user <where> <if test="id !=null and id !=''"> and id=#{id} </if> <if test="userName !=null and userName != ''"> and userName=#{userName} ; </if> </where> </select> </mapper>
执行语句: List<User> users = mapper.selAllUser(null,"赵晓鹏"); 生成的sql: //第一个参数为空被判断掉了,sql中的if标签中的and也没有了,只有后边的,并且有where select * from user WHERE userName=? //如果两个参数都为bull select * from user
choose when:只要有一个成立其他不执行
<mapper namespace="com.xpl.mapper.UserMapper"> <select id="selAllUser" resultType="User"> select * from user <where> <choose> <when test="id !=null and id !=''"> and id=#{id} </when> <when test="userName !=null and userName != ''"> and userName=#{userName} ; </when> </choose> </where> </select> </mapper>
List<User> users = mapper.selAllUser(null,"徐沛蕾"); 生成的sql: select * from user WHERE userName=? //两个参数都满足但是只执行了id List<User> users = mapper.selAllUser(1,"徐沛蕾"); 生成的sql: select * from user WHERE id=?
set:
1.生成set
2.去掉最后一个,
<?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"> <!--这里的namesqace必须和mapper接口的全路径名一致--> <mapper namespace="com.xpl.mapper.UserMapper"> <update id="upDateUser" parameterType="User"> update user <set> id=#{id};//这一句的作用就是防止set语句中所有的判断语句不成立导致sql语句出错变成uptate user where id=?这是一个错误的sql语句 <if test="userName != null and userName != ''"> userName=#{userName}, </if> <if test="address != null and address != ''"> address=#{address},//这个逗号不加也不会报错 </if> </set> where id=#{id} </update> </mapper>
int res = mapper.upDateUser(2, "张学良","东北"); 生成的sql: update user SET userName=?, address=? where id=?
trim:
可以选择在前边加上什么去掉什么和后边加上什么去掉什么
<mapper namespace="com.xpl.mapper.UserMapper"> <update id="upDateUser" parameterType="User"> update user <trim prefix="set" suffixOverrides=","> id=#{id}, <if test="userName !=null and userName !=''"> userName=#{userName}, </if> <if test="address !=null and address !=''"> address=#{address}, </if> </trim> where id=#{id} </update> </mapper>
int res = mapper.upDateUser(1, "刘德华","香港"); 生成sql: update user set id=?, userName=?, address=? where id=?
补充知识:
模糊匹配输入值和数据库值需要添加前缀或者后缀的解决方案
方式一:代码中编写
<mapper namespace="com.xpl.mapper.UserMapper"> <select id="mhupipei" parameterType="string" resultType="User"> select * from user where userName like #{userName}; </select> </mapper>
String userName="%"+"1"+"%";
List<User> users = mapper.mhupipei(userName);
方式二:通过动态sql:bind
<mapper namespace="com.xpl.mapper.UserMapper"> <select id="mhupipei" parameterType="string" resultType="User"> <bind name="userName" value="'%'+userName+'%'"/> select * from user where userName like #{userName}; </select> </mapper>
String userName="1";
List<User> users = mapper.mhupipei(userName);
方式三:使用${}
<mapper namespace="com.xpl.mapper.UserMapper"> <select id="mhupipei" parameterType="string" resultType="User"> select * from user where userName like '%${userName}%' </select> </mapper>
String userName="1"; List<User> users = mapper.mhupipei(userName);
方式四:(使用sql关键字concat)
select * from book where name like concat ('%',#{name},'%')
bind:(重新赋值)
一:模糊查询
二:需要在传入的值的前后加内容
<mapper namespace="com.xpl.mapper.UserMapper"> <select id="mhupipei" parameterType="string" resultType="User"> <bind name="userName" value="'%'+userName+'%'"/> select * from user where userName like #{userName}; </select> </mapper>
String userName="1";
List<User> users = mapper.mhupipei(userName);
知识补充:多条件模糊查询
User user =new User(); user.setId(2); user.setUserName(""); user.setAddress("2"); List<User> users = mapper.mhupipei(user); for (User u:users) { System.out.println(u); } sqlSession.commit();
方式一:
注意绑定的位置!!! <mapper namespace="com.xpl.mapper.UserMapper"> <select id="mohupipei" parameterType="User" resultType="User"> select * from user <where> <if test="id !=null and id !=''"> <bind name="id" value="'%'+id+'%'"/> and id like #{id} </if> <if test="userName !=null and userName !=''"> <bind name="userName" value="'%'+userName+'%'"/> and userName like #{userName} </if> <if test="address !=null and address !=''"> <bind name="address" value="'%'+address+'%'"/> and address like #{address} </if> </where> </select> </mapper>
方式二:
<mapper namespace="com.xpl.mapper.UserMapper"> <select id="mohupipei" resultType="User" parameterType="User"> select * from user <where> <if test="id !=null and id !=''"> and id like '%${id}%' </if> <if test="userName !=null and userName !=''"> and userName like '%${userName}%' </if> <if test="address !=null and address !=''"> and address like '%${address}%' </if> </where> </select> </mapper>
foreach:
功能:
循环参数内容,还具备在内容的前后添加内容,还具备添加分隔符功能.
适用场景:
in 查询中.批量新增中(mybatis 中 foreach 效率比较低)
List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(4); List<User> users = mapper.getUserByForeach(list); for (User u:users) { System.out.println(u); }
<mapper namespace="com.xpl.mapper.UserMapper"> <select id="getUserByForeach" resultType="User" parameterType="list"> select * from USER where id in <foreach collection="list" item="id" open="(" close=")" separator=","> #{id} </foreach> </select> </mapper>
sql片段:
相当于是一个sql字段复用
List<User> users = mapper.sqlRefId(); for (User u:users) { System.out.println(u); }
<mapper namespace="com.xpl.mapper.UserMapper"> <select id="sqlRefId" resultType="User" parameterType="list"> select <include refid="temp"></include> from user </select> <sql id="temp"> id,userName,address </sql> </mapper>
十五:缓存
1. 应用程序和数据库交互的过程是一个相对比较耗时的过程
2. 缓存存在的意义:让应用程序减少对数据库的访问,提升程序运行效率
3. MyBatis 中默认 SqlSession 缓存开启
3.1 同一个 SqlSession 对象调用同一个<select>时,只有第一次访问数据库,第一次之后把查询结果缓存到 SqlSession 缓存区(内存)中
3.2 缓存的是 statement 对象.(简单记忆必须是用一个<select>)
3.2.1 在 myabtis 时一个<select>对应一个 statement 对象
3.3 有效范围必须是同一个 SqlSession 对象
4. 缓存流程
4.1 步骤一: 先去缓存区中找是否存在 statement
4.2 步骤二:返回结果
4.3 步骤三:如果没有缓存 statement 对象,去数据库获取数据
4.4 步骤四:数据库返回查询结果
4.5 步骤五:把查询结果放到对应的缓存区中
5. SqlSessionFactory 缓存
5.1 又叫:二级缓存
5.2 有效范围:同一个 factory 内哪个 SqlSession 都可以获取
5.3 什么时候使用二级缓存:
5.3.1 当数据频繁被使用,很少被修改
5.4 使用二级缓存步骤
5.4.1 在 mapper.xml 中添加
5.4.2 如果不写 readOnly=”true”需要把实体类序列化
5.5 当 SqlSession 对象 close()时或 commit()时会把 SqlSession 缓存的数据刷(flush)到 SqlSessionFactory 缓存区中
十六:多表联查
有三种方式:
1.业务装配
就是在service层中查出一个,之后根据查出来的数据进行查询(N+1)
2.AutoMapping,
通过别名完成映射
3.通过resultMap来完成
属性含有有对象:
1.单个对象
2.集合对象
ResultMap原理:默认使用空参构造(常用方式)也可以使用有参构造(但是实体类中必须有对应的有参构造)以后的例子都是用无参,这里只做有参的说明
<resultMap id="myResultMap" type="User"> <constructor> <idArg column="id" name="arg0" javaType="java.lang.Integer" jdbcType="INTEGER"/> <arg column="userName" name="arg1" javaType="java.lang.String" jdbcType="VARCHAR"/> <arg column="address" name="arg2" javaType="java.lang.String" jdbcType="VARCHAR"/> <arg column="tid" name="arg3" javaType="int" jdbcType="INTEGER"/> </constructor> </resultMap>
要想参数不写arg0...这种形式,就必须在传值的时候使用@parmar(“name”)
<resultMap id="myResultMap" type="User"> <constructor> <idArg column="id" name="id" javaType="java.lang.Integer" jdbcType="INTEGER"/> <arg column="userName" name="userName" javaType="java.lang.String" jdbcType="VARCHAR"/> <arg column="address" name="address" javaType="java.lang.String" jdbcType="VARCHAR"/> <arg column="tid" name="tid" javaType="int" jdbcType="INTEGER"/> </constructor> </resultMap>
一)单表resultMap:解决数据库字段名称和实体类名不一致问题
数据库表设计:
public class User { private String userName1; private String address1; private int id1;
<mapper namespace="com.xpl.mapper.UserMapper"> <resultMap id="myResultMap" type="User"> <id column="id" property="id1"/> <result column="userName" property="userName1"/> <result column="address" property="address1"/> </resultMap> <select id="selAll" resultMap="myResultMap"> select * from user; </select> </mapper>
List<User> users = mapper.selAll();
二:多表resultMap:(关联一个对象)
public class Teacher { private int id; private String name; public class User { private String userName; private String address; private int id; private int tid; private Teacher teacher;
<mapper namespace="com.xpl.mapper.UserMapper"> //查的是什么这里类型是什么 <resultMap id="myResultMap" type="User"> 下边前两行可以不配置,但是第三行一定要配上,因为mybatis只会讲关联的字段装配一次 1.<id column="id" property="id"/> 2.<result column="userName" property="userName"/> 3.<result column="tid" property="tid"/> <association> 装配一个对象时使用 property: 对象在类中的属性名 select:通过哪个查询查询出这个对象的信息 column: 把当前表的哪个列的值做为参数传递给另一个查询 <association property="teacher" column="tid" select="com.xpl.mapper.TeacherMapper.selTeacherBytid"/> </resultMap> <select id="selAll" resultMap="myResultMap" parameterType="int"> select * from user where id=#{id}; </select> </mapper>
<mapper namespace="com.xpl.mapper.TeacherMapper"> <select id="selTeacherBytid" resultType="Teacher" parameterType="int"> select * from teacher where id=#{id}; </select> </mapper>
public interface TeacherMapper { Teacher selTeacherBytid(@Param("id") int id); }
public interface UserMapper { List<User> selAll(@Param("id")int id); }
List<User> users = mapper.selAll(1);
二:多表resultMap:(关联一个list)
public class User { private String userName; private String address; private int id; private int tid; private Teacher teacher; public class Teacher { private int id; private String name; private List<User> users;
<mapper namespace="com.xpl.mapper.UserMapper"> 这里没有resultMap所以是resultType <select id="selAll" parameterType="int" resultType="user"> select * from user where tid=#{tid}; </select> </mapper>
<mapper namespace="com.xpl.mapper.TeacherMapper"> <resultMap id="myresultMap" type="teacher"> <id column="id" property="id"/> <result column="name" property="name"/> users对应老师中的list属性名 id:想要将teacher中的那一列的值拿来进行user查询 select对应user的查询 <collection property="users" column="id" ofType="user" select="com.xpl.mapper.UserMapper.selAll"/> </resultMap> <select id="selTeacher" resultMap="myresultMap" > select * from teacher; </select> </mapper>
多表联查加载的是先查询的那个mapper.xml TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); List<Teacher> teachers = mapper.selTeacher();
注意:这里可以将关联的那个resultMap抽取出来继承
<mapper namespace="com.xpl.mapper.TeacherMapper"> <resultMap id="myresultMap" type="teacher"> <id column="id" property="id"/> </resultMap> <resultMap id="myresultMap2" type="teacher" extend="myresultMap”> users对应老师中的list属性名 id:想要将teacher中的那一列的值拿来进行user查询 select对应user的查询 <collection property="users" column="id" ofType="user" select="com.xpl.mapper.UserMapper.selAll"/> </resultMap> <select id="selTeacher" resultMap="myresultMap" > select * from teacher; </select> </mapper>
三:多表联合查询(常用方式)
和上边的二对比:
查询谁就写谁的mapper.xml和接口不需要写两个
<mapper namespace="com.xpl.mapper.TeacherMapper"> <!--注意因为我们的sql中起了别名,所以这里我们的column中要和sql】中的别名一致--> <!--property中的和类名一致--> <resultMap id="myresultMap" type="teacher"> <id column="tid" property="id"/> <result column="tname" property="name"/> <collection property="users" ofType="user"> <id column="uid" property="id"/> <result column="uname" property="userName"/> <result column="uaddress" property="address"/> <result column="utid" property="tid"/> </collection> </resultMap> <select id="selTeacher" resultMap="myresultMap" > select t.id tid,t.name tname,u.id uid,u.userName uname,u.address uaddress,u.tid utid from teacher t left join user u on t.id=u.tid; </select> </mapper>
四:AutoMapping加载单个对象这种方式只能resultType而不能resultMap
起个别名:然后遇到对象属性就通过`teacher.name` 来赋值
<mapper namespace="com.xpl.mapper.UserMapper"> <select id="selAll" resultType="User"> select u.id id,u.userName userName,u.address address,u.tid tid,t.name `teacher.name`,t.id `teacher.id` from user u left join teacher t on t.id=u.tid; </select> </mapper>
五:延迟加载
(懒加载,实体类中有其他属性,在sql中又引用了其他的sql,name我们在text中查询的时候,不去是用那个相关的属性,
就只会执行主的那一条sql,只用使用了相关联的属性,懒加载才会去加载关联的sql)
配置可以在mapper.xml中fetchType="lazy"也可以在全局配置文件中配置
<mapper namespace="com.xpl.mapper.UserMapper"> //查的是什么这里类型是什么 <resultMap id="myResultMap" type="User"> 下边前两行可以不配置,但是第三行一定要配上,因为mybatis只会讲关联的字段装配一次 1.<id column="id" property="id"/> 2.<result column="userName" property="userName"/> 3.<result column="tid" property="tid"/> <association> 装配一个对象时使用 property: 对象在类中的属性名 select:通过哪个查询查询出这个对象的信息 column: 把当前表的哪个列的值做为参数传递给另一个查询 <association property="teacher" column="tid" select="com.xpl.mapper.TeacherMapper.selTeacherBytid" fetchType="lazy"/> </resultMap> <select id="selAll" resultMap="myResultMap" parameterType="int"> select * from user where id=#{id}; </select> </mapper>
十七:配置类型转换器
看图理解:
实现:
一)实体类
public class People { private String name; private String address; private List<String> likes;
二)配置转换器类
package com.xpl.handler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedJdbcTypes; import org.apache.ibatis.type.MappedTypes; import org.apache.ibatis.type.TypeHandler; import java.sql.*; import java.util.Arrays; import java.util.List; @MappedJdbcTypes(JdbcType.VARCHAR) @MappedTypes(List.class) public class ListHandler implements TypeHandler<List<String>> { // 这里的操作就是原生jdbc中的动态的设置值 public void setParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException { StringBuffer sb=new StringBuffer(); // parameter是我传进来的参数集合,我需要将他遍历出来,一个一个的a按照下标给她赋值 for(String s:parameter){ sb.append(s).append(","); } // 给PreparedStatement设置值 需要两个参数:下标,列名 ps.setString(i,sb.toString()); } // 下边三个是重载方法,用来读取的时候使用 public List<String> getResult(ResultSet rs, String columnName) throws SQLException { // 将查询结果中的值按照列名读出来 String string = rs.getString(columnName); // 将上边的字符串变成数组,按照,划分 String[] strings = string.split(","); // 将数组变成集合,看类中定义的是什么,我的类中定义的是list就变成list,如果定义的是数组,直接返回就可以了 List<String> values = Arrays.asList(strings); return values; } public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException { // 将查询结果中的值按照列名读出来 String string = rs.getString(columnIndex); // 将上边的字符串变成数组,按照,划分 String[] strings = string.split(","); // 将数组变成集合,看类中定义的是什么,我的类中定义的是list就变成list,如果定义的是数组,直接返回就可以了 List<String> values = Arrays.asList(strings); return values; } /** * 这个是用来调用存储过程的时候使用 * @param cs * @param columnIndex * @return * @throws SQLException */ public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException { // 将查询结果中的值按照列名读出来 String string = cs.getString(columnIndex); // 将上边的字符串变成数组,按照,划分 String[] strings = string.split(","); // 将数组变成集合,看类中定义的是什么,我的类中定义的是list就变成list,如果定义的是数组,直接返回就可以了 List<String> values = Arrays.asList(strings); return values; } }
三)mapper
<?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.xpl.model.People"> <insert id="addPeople" parameterType="People"> insert into people set name=#{name},address=#{address},likes=#{likes,typeHandler=com.xpl.handler.ListHandler} </insert> <select id="selPeople" parameterType="string" resultType="People"> select * from people where name=#{name}; </select> </mapper>
四)test
添加时:这里使用的是mybatis全局配置文件配置的转换器
public class Test { public static void main(String[] args) throws IOException { // 通过流加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml"); // 通过构建模式来创建SqlsessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 打开session SqlSession sqlSession = sqlSessionFactory.openSession(); // 执行sql List<String> list = new ArrayList<String>(); list.add("足球"); list.add("排球"); list.add("篮球"); People people = new People(); people.setName("xu"); people.setAddress("宝安"); people.setLikes(list); int res = sqlSession.insert("com.xpl.model.People.addPeople", people); System.out.println(res); // 记得提交和关闭 sqlSession.commit(); sqlSession.close(); } }
读取时:这里的转换器在mapper.xml文件中添加
package com.xpl.test; import com.xpl.model.People; import com.xpl.model.User; 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.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) throws IOException { // 通过流加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml"); // 通过构建模式来创建SqlsessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 打开session SqlSession sqlSession = sqlSessionFactory.openSession(); // 执行sql People res = sqlSession.selectOne("com.xpl.model.People.selPeople", "xu"); System.out.println(res); // 记得提交和关闭 sqlSession.commit(); sqlSession.close(); } }
Mybatis-conf.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--> <properties resource="db.properties"/> <!--取别名--> <typeAliases> <!--这种方式只能一个一个类的起别名,太麻烦--> <!--<typeAlias type="com.xpl.model.User" alias="User"/>--> <!--这种方式可以指定给某个包下的所有类起别名,并且可以写多个package--> <package name="com.xpl.model"/> </typeAliases> <!--配置类型转换器:注意他的位置!!!!!,跳坑了!!!--> <typeHandlers> <!--这个是在读取的时候需要用到--> <!--包扫描,将所有的类型转换器都扫进来--> <!--<package name="com.xpl.handler"/>--> <!--一个一个的配置类型转换器--> <typeHandler handler="com.xpl.handler.ListHandler"/> </typeHandlers> <!--default随意起名字,value是下边environment的id意思就是当前使用的数据源是什么--> <environments default="development"> <!--environment可以配置多个数据源--> <environment id="development"> <!-- 使用原生JDBC事务 --> <transactionManager type="JDBC"/> <!--type中pooled是数据连接池--> <!--<dataSource type="POOLED">--> <!--<property name="driver" value="com.mysql.jdbc.Driver"/>--> <!--<property name="url" value="jdbc:mysql:///test"/>--> <!--<property name="username" value="root"/>--> <!--<property name="password" value="root"/>--> <!--</dataSource>--> <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> <!--配置mapper.xml文件到mybatis中--> <mappers> <!--resource是mapper.xml的全文件名(区别于全路径名,全路径名中间是".",这里是"/"),适用于,多个mapper.xml文件一个一个的精准的写入--> <mapper resource="com/xpl/mapper/UserMapper.xml"/> <!--这种方式用来和接口式编程的方式配合,扫描mapper接口所在德包--> <!--<package name="com.xpl.mapper"/>--> </mappers> </configuration>
十八:主键回填:
因为可能在某些情况下id或者某些字段是自动生成的,比如取餐码等等,回填到刚才插入的那个对象中,
有两种:
1)数据库的字段自增长
2)数据库字段是我们在代码中自动生成的uuid
方式一(自增长)
<mapper namespace="com.xpl.model.User"> <insert id="addUser" parameterType="User"> -- 回填的列名 插入前返回还是插入后返回 java类型 数据库列名 java列名 <selectKey order="AFTER" resultType="java.lang.Integer" keyColumn="id" keyProperty="id"> select last_insert_id() </selectKey> insert into user set userName=#{userName},address=#{address},id=#{id},tid=#{tid}; </insert> </mapper>
public class Test { public static void main(String[] args) throws IOException { // 通过流加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml"); // 通过构建模式来创建SqlsessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 打开session SqlSession sqlSession = sqlSessionFactory.openSession(); // 执行sql User user = new User(); user.setUserName("krrrr"); user.setAddress("zhoukou"); user.setTid(1); // UserW userW = new UserW(); // userW.setUser(user); int res = sqlSession.insert("com.xpl.model.User.addUser", user); System.out.println(res); System.out.println(user); sqlSession.commit(); sqlSession.close(); } }
发现上边的输出语句,打印插入的那个没有设置id值的user,已经将插入时自定生成的id值回填到user中了;
简化写法:
<mapper namespace="com.xpl.model.User"> <insert id="addUser" parameterType="User" useGeneratedKeys="true" keyProperty="id" keyColumn="id" > insert into user set userName=#{userName},address=#{address},id=#{id},tid=#{tid}; </insert> </mapper>
方式二:uuid
<mapper namespace="com.xpl.model.User"> <insert id="addUser" parameterType="User"> -- 回填的列名 插入前返回还是插入后返回 java类型 数据库列名 java列名 <selectKey order="BEFORE" resultType="java.lang.String" keyColumn="userName" keyProperty="userName"> select uuid() </selectKey> insert into user set userName=#{userName},address=#{address},id=#{id},tid=#{tid}; </insert> </mapper>
public class Test { public static void main(String[] args) throws IOException { // 通过流加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml"); // 通过构建模式来创建SqlsessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 打开session SqlSession sqlSession = sqlSessionFactory.openSession(); // 执行sql User user = new User(); user.setAddress("zhoukou"); user.setTid(1); user.setId(10); // UserW userW = new UserW(); // userW.setUser(user); int res = sqlSession.insert("com.xpl.model.User.addUser", user); System.out.println(res); System.out.println(user);// 记得提交和关闭 sqlSession.commit(); sqlSession.close(); } }
十九:注解
简化xml的配置
代码和sql耦合度太高,不方便,所以这里就先不写了
二十:Mybatis运行原理
MyBatis运行开始时需要先通过 Resources 加载全局配置文件.下面需要实例化SqlSessionFactoryBuilder构建器.帮助SqlSessionFactory接口实现类DefaultSqlSessionFactory.
实例化DefaultSqlSessionFactory之前需先创建 XmlConfigBuilder解析全局配置文件流,并把解析结果存放在 Configuration 中.之后把Configuratin传递给DefaultSqlSessionFactory.到此SqlSessionFactory工厂创建成功.
SqlSessionFactory 工厂创建 SqlSession. 每次创建 SqlSession 时,都需要由 TransactionFactory 创建 Transaction对象,同时还需要创建 SqlSession 的执行器 Excutor,最后实例化DefaultSqlSession,传递给 SqlSession 接口. 根据项目需求使用 SqlSession 接口中的 API 完成具体的事务操作. 如果事务执行失败,需要进行 rollback 回滚事务. 如果事务执行成功提交给数据库.关闭 SqlSession