赞
踩
MyBatis本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis. 2013年11月迁移到Github. iBATIS 一词来源于"internet"和"abatis"的组合,是一个基于Java的持久层框架。iBATIS 提供的持久层框架包括SQL Maps和Data Access Objects (DAO)。
Mybatis基于java的持久层框架,它的内部封装了JDBC,让开发人员只需要关注SQL语句本身,不需要花费精力在驱动的加载、连接的创建、Statement的创建等
复杂的过程。
Mybatis通过XML或注解的方式将要执行的各种的statement配起来,并通过java对象和statement中的sq|的动态参 数进行映射生成最终执行的SQL语句,最后由mybatis框架执行SQL,并将结果直接映射为java对象。
采用了ORM思想解决了实体类和数据库表映射的问题。对JDBC进行了封装,屏蔽了JDBC API底层的访问细节,避免我们与jdbc的api打交道, 就能完成对数据的持久化操作。
o--0bject java对象
R--Relation 关系,就是数据库中的一张表
M--mapping映射
1、数据库连接的创建、释放连接的频繁操作造成资源的浪费从而影响系统的性能。
2、SQL语句编写在代码中,硬编码造成代码不容易维护,实际应用中SQL语句变化的可能性比较大,一旦变动就需要改变]ava类。
3、使用preparedStatement的时候传递参数使用占位符,也存在硬编码,因为SQL语句变化,必须修改源码。
4、对结果集的解析中也存在硬编码。
CREATE TABLE team (
`teamId` int NOT NULL AUTO_INCREMENT COMMENT '球队ID',
`teamName` varchar(50) DEFAULT NULL COMMENT '球队名称',
`location` varchar(50) DEFAULT NULL COMMENT '球队位置',
`createTime` date DEFAULT NULL COMMENT '球队建立时间',
PRIMARY KEY (`teamId`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
<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>8.0.23</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
一般情况下配置文件的名称可以自定义,这里使用mybatis.xml。配置文件放在java/resources中。
头文件取官网中复制粘贴(Mybatis官网)
<?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> <!--配置mybatis的环境--> <environments default="development"> <!--id:数据源名称--> <environment id="development"> <!--事务的类型:JDBC,使用Connection 对象的提交和回滚的方法--> <transactionManager type="JDBC"/> <!--创建数据源:POOLED方式 使用连接--> <dataSource type="POOLED"> <!--创建数据源的必备四参数 注意这里的数据库版本是8,如果是5的话,driver和url都不一样--> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mydb?usUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!--在配置文件中注册映射文件:因为程序运行时只读取配置文件--> <mappers> <mapper resource="com/zx/pojo/team.xml"/> </mappers> </configuration>
创建Team实体类,和数据库中的表结构保持一致
com.zx.pojo.team.xml顾名思义,此文件专门用于做关于team表和Team类的映射、CRUD
<?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"> <!--映射文件:实体类与数据库表的映射 ORM思想 object relation mapping SQL语句从代码的硬编码中抽取出来了 --> <!--namespace="实体类的完全限定名"--> <mapper namespace="com.zx.pojo.Team"> <!--id:自定义,不能重复,相当于原来的dao中的方法名 resultType=“返回的类型,如果是集合,写的是集合中元素的类型”;使用要求:实体类中的属性和表中的列名一致 --> <select id="queryAll" resultType="com.zx.pojo.Team"> select * from team; </select> </mapper>
程序运行时只会扫描配置文件mybatis.xml,因此需要在mybatis.xm注册映射文件team.xml
<!--在配置文件中注册映射文件:因为程序运行时只读取配置文件-->
<mappers>
<mapper resource="com/zx/pojo/team.xml"/>
</mappers>
添加在标签下
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes>
<include>**/*/properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
//编写before和after,在所有测试方法执行前获取连接并在方法执行后关闭连接。 /** * 类名: TestTeam * 作者: ZX * 简介: 测试类 */ public class TestTeam { private String resource = "mybatis.xml"; SqlSession sqlSession = null; @Before public void before(){ //读取配置文件 Reader reader = null; try { reader = Resources.getResourceAsReader(resource); } catch (IOException e) { e.printStackTrace(); } //创建工厂 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);//根据图纸创建出工厂 //获取连接 sqlSession = factory.openSession(); } }
@Test
public void test01() {
//执行sql
List<Team> teamList = sqlSession.selectList("com.zx.pojo.Team.queryAll");
//遍历结果
for(Team team : teamList){
System.out.println(team);
}
}
<!--parameterType:表示参数的类型,参数唯一是使用,可以省略,框架可以自己判定参数类型
#{自定义的参数名称} 该名称没有实际意义,符合命名规则就好
-->
<select id="queryById" parameterType="java.lang.Integer" resultType="com.zx.pojo.Team">
select * from team where teamId=#{id};
</select>
@Test
public void test02() {
Team team = sqlSession.selectOne("com.zx.pojo.Team.queryById",2);
System.out.println(team);
}
<!--添加一个对象
parameterType="com.zx.pojo.Team" 将对象作为参数
#{值}必须是实体类中的属性名称
-->
<insert id="add" parameterType="com.zx.pojo.Team">
INSERT INTO `mydb`.`team` (`teamName`, `location`, `createTime`)
VALUES (#{teamName}, #{location}, #{createTime});
</insert>
@Test
public void test02() {
Team team = new Team();
team.setTeamName("猴子队");
team.setLocation("北京");
team.setCreateTime(new Date());
sqlSession.insert("com.zx.pojo.Team.add",team);//增删改必须手动提交事务,否则不生效
//sqlSession.commit();//提交事务
}
注意,由于mybatis之前我们配置的事务类型
JDBC会将事务的自动提交设置为false,所以增删改操作必须手动提交事务才能生效。
当然,如果不想手动提交,也可以设置为true,在获取连接时设置参数true
//获取连接
sqlSession = factory.openSession(true);
引入依赖,然后再resources添加log4j.properties文件
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
# Global logging configuration info warning error
#日志级别
log4j.rootLogger=DEBUG,stdout
#Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
mybatis.xml中添加配置
<!--配置日志:要注意顺序:settings节点要在environments节点之前-->
<!--节点的顺序参考:按住ctrl+configuration节点,进入文件就能看到顺序-->
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
<update id="update">
update team set teamName=#{teamName},location=#{location}
where teamId=#{teamId};
</update>
<update id="delete">
delete from team where teamId=#{teamId};
</update>
@Test public void testupdate() { Team team =new Team(); team.setTeamId(1003); team.setTeamName("张三的球队"); team.setLocation("张三球队地址"); team.setCreateTime(new Date()); int i = sqlSession.update("com.zx.pojo.Team.update",team); sqlSession.commit(); System.out.println(i); } @Test public void testdelete() { Team team =new Team(); team.setTeamId(1003); team.setTeamName("张三的球队"); team.setLocation("张三球队地址"); team.setCreateTime(new Date()); int i = sqlSession.delete("com.zx.pojo.Team.delete",team); sqlSession.commit(); System.out.println(i); }
Resources类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返回不同类型的IO流对象。
SqlSessionFactory的创建,需要使用SqlSessionFactoryBuilder对象的build0方法。事实上使用SqlSessionFactoryBuilder的原因是将SqlSessionFactory这个复杂对象的创建交由Builder来执行,也就是使用了建造者设计模式。
建造者模式:又称生成器模式,是-种对象的创建模式。可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品(将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示), 这样用户只需指定需要建造的类型就可以得到具体产品,而不需要了解具体的建造过程和细节。
在建造者模式中,角色分指导者(Director )与建造者(Builder):用户联系指导者,指导者指挥建造者,最后得到产品。建造者模式可以强制实行一种分步骤进行的建造过程。
SqlSessionFactory接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用只需要一个该对象即可。 创建SqISession需要使用SqlSessionFactory接口的的openSession()方法。
默认的openSession(方法没有参数, 它会创建有如下特性的sqlSession:
1、会开启一个事务(也就是不自动提交)。
2、将从由当前环境配置的DataSource实例中获取Connection 对象。事务隔离级别将会使用驱动或数据源的默认设置。
3、预处理语句不会被复用,也不会批量处理更新。
openSession(true):创建一个有自动提交功能的sqlSession
openSession(false):创建一个非自动提交功能的SqlSession, 需手动提交
openSession():同openSession(false)
SqlSession接口对象用于执行持久化操作。一个SqlSession对应着一次数据库会话,一次会话以SqlSession 对象的创建开始,以SqlSession对象的关闭结束。
SqlSession接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其close(方法,将其关闭。再次需要会话,再次创建。SqlSession 在方法内部创建,使用完毕后关闭。
SqISession类中有超过20个方法,我们常用的几乎都是执行语法相关的方法。
这些方法被用来执行定义在SQL映射的XML文件中的SELECT、INSERT. UPDATE 和DELETE语句。它们都会自行解释,每句都使用语句的ID属性和参数对象,参数可以是原生类型(自动装箱或包装类)、JavaBean、POJO 或Map.
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter )
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
/*
selectOne和selectList的不同仅仅是selectOne必须返回一个对象或null值。如果近回值多于一个, 那么就会抛出异常。
selectMap稍微特殊一点, 因为它会将返回的对象的其中一个属性作为key值,将对象作为value 值,从而将多结果集转为Map类型值。
因为并不是所有语句都需要参数,所以这些方法都重载成不需要参数的形式。
*/
1、Mybatis.xml文件是mybatis框架的全局配置文件,配置了mybatis框架运行的环境等信息。
Mapper1.xml.....是SQL的映射文件,文件中配置了所有的操作数据库的sql语句,这些文件需要在全局配置文件中加载。
2、通过mybatis环境等配置信息构建SqlSessionFactory,相当于是产生连接池
3、由会话工厂创建SqlSession即会话(连接),操作数据库需要通过sqlSession进行的。
4、Mybatis底层自定义了Executor执行器的接口操作数据库,Executor接口有两个实现,一个基本的执行器,一个是缓存的执行器。
5、Mapped statement 也是mybatis框架一个底层的封装对象,他包装了mybatis配置信息以及sql映射信息。Mapper.xml文件中的一个SQL语句对应一个Mapped statement对象,sql的id就是Mapped statement的id.
6、Mapped statement对SQL执行输入参数的定义,输入参数包括HashMap、基本类型、pojo,Executor通过Mapped statement躺在执行SQL语句前将输入java对象映射到sql语句中,执行完毕SQL之后,输出映射就是JDBC编码中的对preparedstatement执行结果的定义。
ThreadLocal并非是一个线程的本地实现版本, 它并不是一个Thread, 而是threadlocalvariable(线程局部变量)。 也许把它命名为ThreadLocalVar更加合适。 线程
局部变量(ThreadLocal)其实的功用非常简单,就是为每个使用该变量的线程都提供一个变量值的副本, 是Java中一 种较为特殊的线程绑定机制, 是每一个线程都
可以独立地改变自己的副本,而不会和其它线程的副本冲突。
ThreadLocal类似List,可以将其看作一个容器,只不过这个容器只能装一个元素。
由于之前提到的SqlSession时线程不安全的,所以需要使用ThreadLocal来对其线程安全问题进行优化。
使用了ThreadLocal的Mybatis工具类如下:
package com.zx.util; 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.Reader; /** * 类名: MybatisUtil * 作者: ZX * 简介: Mybatis工具类:获取连接和关闭连接 */ public class MybatisUtil { private static ThreadLocal<SqlSession> sqlSessionThreadLocal = new ThreadLocal<>(); private static SqlSessionFactory factory; static { Reader reader = null; try { reader = Resources.getResourceAsReader("mybatis.xml"); } catch (IOException e) { e.printStackTrace(); } //创建工厂 factory = new SqlSessionFactoryBuilder().build(reader); } /** * 获取连接 * @return */ public static SqlSession getSqlSession(){ //从ThreadLocal中获取 SqlSession sqlSession = sqlSessionThreadLocal.get(); if(sqlSession == null) { //创建sqlSession sqlSession = factory.openSession(); //将SqlSession与线程绑定 sqlSessionThreadLocal.set(sqlSession); } return sqlSession; } /** * 关闭连接 */ public static void closeSqlSession(){ //从ThreadLocal中获取 SqlSession sqlSession = sqlSessionThreadLocal.get(); if(sqlSession != null){ sqlSession.close(); sqlSessionThreadLocal.remove(); } } }
在Dao的实现类中使用此工具类获取连接对数据库进行操作即可。
通过上面原有Dao方式开发,我们发现,Dao似乎并没有做什么实质性的工作,连接是由工具类完成的,与数据库的交互是由配置文件mapper中相应的sql语句完成的,dao层完全可以省略。
所以Mybatis框架抛开了Dao的实现类,直接定位到映射文件mapper中相应的sql语句,对DB进行操作,这种对Dao的实现方式称为Mapper接口的动态代理方式。
Mapper的动态代理方式无需程序员实现Dao接口,接口是由Mybatis结合映射文件自动生成的动态代理实现的。
理解:使用Mapper接口的方式,就是定义一个Mapper接口,内容和dao接口完全一致,但是它没有实现类,实现它的方式就是同名的xml映射文件Mapper.xml
//编写Mapper接口
package com.zx.mapper;
public interface TeamMapper {
List<Team> queryAll();
Team queryById(Integer teamId);
int add(Team team);
int update(Team team);
int del(Integer teamId) ;
}
Mapper.xml必须与Mapper接口同包同名,如果要将Mapper.xml放在resources文件夹下,也要创建相同路径的包
注意,与src/main/java文件夹不同,resources文件夹下的包路径要一层层创建才可以。
<?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.zx.mapper.TeamMapper"> <select id="queryAll" resultType="com.zx.pojo.Team"> select * from team; </select> <select id="queryById" parameterType="java.lang.Integer" resultType="com.zx.pojo.Team"> select * from team where teamId=#{id}; </select> <insert id="add" parameterType="com.zx.pojo.Team"> INSERT INTO `mydb`.`team` (`teamName`, `location`, `createTime`) VALUES (#{teamName}, #{location}, #{createTime}); </insert> <update id="update"> update team set teamName=#{teamName},location=#{location} where teamId=#{teamId}; </update> <update id="del"> delete from team where teamId=#{teamId}; </update> </mapper>
可以将Mapper.xml理解为Mapper接口的实现类,它是由系统在运行时根据映射文件内容动态生成。
测试类:
public class TestTeamMapper { //通过动态代理的方式产生实现类 private TeamMapper teamMapper = MybatisUtil.getSqlSession().getMapper(TeamMapper.class); @Test public void testQueryByID(){ Team team = teamMapper.queryById(1005); System.out.println(team); } @Test public void testDelete(){ int i = teamMapper.del(1005); MybatisUtil.getSqlSession().commit();//事务提交 System.out.println(i); } }
getMapper方法的底层是通过jdk的动态代理实现的
在insert一条数据后,一般情况下我们不会插入id值,因为它是自增的,数据库会自动生成。如何实现插入一条数据后立即获取到它的id值呢?
实现方法:insert中添加一个标签selectKey,使用mysql中的方法LAST_INSERT_ID()获取最后一条插入的id
<insert id="add" parameterType="com.zx.pojo.Team">
<!--新增成功之后将自增的ID赋值给参数属性teamId
keyProperty:表示新增的id值赋值到哪个属性中
order:AFTER/BRFORE两个取值,表示selectKey中的sql语句在insert语句之前还是之后执行
resultType:表示返回值类型
-->
<selectKey keyProperty="teamId" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
INSERT INTO `mydb`.`team` (`teamName`, `location`, `createTime`)
VALUES (#{teamName}, #{location}, #{createTime});
</insert>
@Test
public void testAdd(){
Team team = new Team();
team.setTeamName("猴子队");
team.setLocation("北京");
team.setCreateTime(new Date());
teamMapper.add(team);
System.out.println(team.getTeamId());//这里就可以获取到刚刚新增数据的id了
MybatisUtil.getSqlSession().commit();//事务提交
}
借助mysql的方法select UUID(),可以生成一条随机的字符串类型id,selectKey参数使用BEFORE,在insert语句之前生成,并将其赋值给GameRecord的主键recordId
</insert>
<insert id= "add" parameterType= "com.kkb.pojo.GameRecord">
<selectKey keyProperty="recordId" order="BEFORE" resultType="java.Lang.String">
select UUID()
</selectKey>
INSERT INTO `mydb`.`gamerecord` ( `recordId`,`homeTeamId`, `score`, `visitingTeamId` )
VALUES (#{recordId}, #{homeTeamId}, #{score}, #{visitingTeamId})
当sql语句需要传递多个对象的参数时,应该如何操作
方法1:
List<Team> queryByRange1(Integer min,Integer max);//TeamMapper接口代码
当参数有多个时,直接使用#{arg0} #{arg1}…或者#{param1} #{param2}…按顺序接受参数即可
<!--细节1:
mybatis3.3版本之前:可以直接写#{0} #{1}
从mybatis3.4开始:要写#{arg0} #{arg1}...或者#{param1} #{param2}...
细节2:
sql语句中不能使用小于号“<”,要使用转义符号,大于号没有限制,也可以使用转义符号
"<" = <
">" = >
-->
<select id="queryByRange1" resultType="com.zx.pojo.Team">
select * from team
where teamId>=#{arg0} and teamId<=#{arg1}
</select>
@Test
public void test01(){
List<Team> teams = teamMapper.queryByRange1(1008,1009);
teams.forEach(team -> System.out.println(team));
}
当然,我们开发时希望能够更直观地看到当前接收的是哪一个参数,因此还可以使用注解的方式:
方法2*(常用):
//使用Param("")注解,value为要映射到xml中的参数名
List<Team> queryByRange2(@Param("min") Integer min, @Param("max")Integer max);
<!--#{}中的名称必须与接口的方法中的参数注解@Param("")的value保持一致-->
<select id="queryByRange2" resultType="com.zx.pojo.Team">
select * from team
where teamId>=#{min} and teamId<=#{max}
</select>
@Test
public void test02(){
List<Team> teams = teamMapper.queryByRange2(1008,1009);
teams.forEach(team -> System.out.println(team));
}
方法3:
还可以传入集合Map,要求#{}中的名称必须与Map中的key保持一致
List<Team> queryByRange3(Map<String, Object> map);
<!--#{}中的名称必须与Map中的key保持一致-->
<select id="queryByRange3" resultType="com.zx.pojo.Team">
select * from team
where teamId>=#{min} and teamId<=#{max}
</select>
@Test
public void test03(){
Map<String,Object> map = new HashMap<>();
map.put("min",1008);
map.put("max",1009);
List<Team> teams = teamMapper.queryByRange3(map);
teams.forEach(team -> System.out.println(team));
}
方法4:
这种方法就类似于之前使用过的传入的单个对象参数Team,当时是可以将Team的所有属性都作为参数传入的,只要#{}中的名称=属性名即可建立映射。
于是我们可以重新创建一个类专门用来存储要传递的参数,直接传入这个类的对象作为参数,xml文件中直接使用#{属性名}接收即可。
代码略,参考5-实现方式中Team属性参数的传递。
实例:
<!--这里的column和columnValue都是字符串类型
#{}相当于占位符?,最终封装为sql字符串中的占位符?,使用columnValue的值填充参数
${}表示直接替换字符串,其值直接拼接到sql字符串中
-->
<select id="queryByField" resultType="com.zx.pojo.Team">
select * from team
where ${column}=#{columnValue}
</select>
List<Team> queryByField(@Param("column")String column,@Param("columnValue")String columnValue);
//测试
@Test
public void test04(){
List<Team> teams = teamMapper.queryByField("location","北京");
teams.forEach(team -> System.out.println(team));
}
//运行时会将sql字符串变成下面的样子:
//DEBUG [main] - ==> Preparing: select * from team where location=?
<!--resultType=“返回的类型,如果是集合,写的是集合中元素的类型”;使用要求:实体类中的属性和表中的列名一致
-->
<select id="queryAll" resultType="com.zx.pojo.Team">
select * from team;
</select>
当查询的结果为单行单列,即只有一个数据时,可以直接使用基本类型作为返回值
<select id="***" resultType="java.lang.Integer">
当结果为单行多列时,使用基本类型,只会显示第一列。
如果查询结果返回了多行,则会报异常:TooManyResultException
当查询结果为单行多列,可以使用Map作为返回类型
<select id="***" resultType="java.util.HashMap">
返回结果也直接用Map类型获取
Map<String,Object> ***();
返回的数据map中,key就是列名,value就是该数据该列的值
返回类型为Map
当查询结果为多行多列,也可以使用Map作为返回类型。
<select id="***" resultType="java.util.HashMap">
但是返回结果用List<Map<String,Object>>来获取,因为结果是多行。
并且之前提过,resultType属性:如果返回结果是集合,写的是集合中元素的类型
使用resultType,就相当于默认表中的列名与对应得实体类属性名是一致的。
创建resultMap,相当于自己编写表中的列与实体类中属性的映射关系。
resultMap和resultType不能同时出现。
首先建立映射关系,也就是创建一个创建resultMap:
<!--创建resultMap:数据库表的列 和 实体类的属性 的映射关系 id:resultMap的名称,自定义,唯一 type:要映射的java实体类 --> <resultMap id="baseResultMap" type="com.zx.pojo.Team"> <!--一般主键用id标签,其余的列用result标签 column属性:表示数据库表的列名,不区分大小写 property属性:表示实体类中的对应的属性名,区分大小写 javaType属性:实体类中的对应属性的类型,可以省略,mybatis会自己推断 jdbcType属性:数据库中的列的类型 一般省略 --> <id column="teamId" property="teamId" javaType="java.lang.Integer"></id> <result column="teamName" property="teamName" javaType="java.lang.String"></result> <result column="location" property="location" javaType="java.lang.String"></result> <result column="createTime" property="createTime" javaType="java.util.Date"></result> </resultMap>
然后在select语句中引用它的id,这里以queryAll为例
之前的queryAll
<select id="queryAll" resultType="com.zx.pojo.Team">
select * from team;
</select>
将resultType改为resultMap,并引用刚才创建的resultMap的id
<select id="queryAll2" resultMap="baseResultMap">
select * from team;
</select>
测试
@Test
public void testQueryAll2(){
List<Team> teams = teamMapper.queryAll2();
teams.forEach(team -> System.out.println(team));
}
比如数据库中列名为user_id,对应数据库中的属性名为userId。
我们返回类型还是使用resultType指定实体类,但是在编写select语句时,这样编写
select user_id as userId from 表名;
如上,查询到的视图列名就和数据库中的属性名一致了
实例:
select标签中引用下面的resultMap
<resultMap id="baseMap" type="com.zx.pojo.Users">
<id coLumn="user_id" property="userId"/>
</resuleMap>
案例中使用的mybatis .xml就是Mybatis的全局配置文件。
全局配置文件需要在头部使用约束文件
<?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的配置文件包含了会深深影响MyBatis行为的设置和属性信息。配置文档的顶层结构如下:
configuration (配置)
properties--属性:加载外部的配置文件,例如加载数据库的连接信息
Settings--全局配置参数:例如日志配置
typeAliases--类型别名
typeHandlers----类型处理器
objectFactor-----对象工厂
Plugins------插件:例如分页插件
Environments----环境集合属性对象
environment (环境变量)
transactionManager (事务理器)
dataSource (数据源)
Mappers---映射器:注册映射文件用
属性可以在外部进行配置,并可以进行动态替换。我们既可以在properties元素的子元素中设置(例如DataSource节点中的properties节点) ,也可以在Java属性文件中配这些属性。
数据源中有连接数据库的四个参数数据,我们一般都是放在专门的属性文件中,mybatis的全局配置文件直接从属性文件中读取数据即可。
1、在resources目录创建jdbc.properties文件,文件名称可以自定义。注意这里的&就是&,不需要用转义符
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?usUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
jdbc.username=root
jdbc.password=123456
2、mybatis的全局配置文件引入属性文件
<properties resource="jdbc.properties"/>
3、使用属性文件中的值
<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>
MyBatis中极为重要的调整设置,它们会改变MyBatis的运行时行为,例如我们配的日志就是应用之一。其余内容参考官网文档
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
一个配置完整的 settings 元素的示例如下(官网上的):
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
<typeAliases>
<!--当这样配置时,`Blog` 可以用在任何使用 domain.blog.Blog 的地方。-->
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<!--也可以指定一个包名,MyBatis会在包名下面搜索需要的Java Bean,使用别名时直接使用类名的首字母大小写形式都可以-->
<package name="domain.blog"/>
</typeAliases>
每一个在包 domain.blog
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author
的别名为 author
;若有注解,则别名为其注解值。见下面的例子:
@Alias("author")
public class Author {
...
}
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
配置有多种方式,选哪种都可以
<mappers>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
要求接口和映射文件同包同名(同包指的是路径相同)
<mappers>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
要求接口和映射文件同包同名(同包指的是路径相同)
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
Mybatis中访问数据库支持连接池技术,而且是采用的自己的连接池技术。在Mybatis的mybatis.xml配置文件中,通过来实现Mybatis中连接池的配置。MyBatis 在初始化时,根据的type属性来创建相应类型的的数据源DataSource.
Mybatis的数据源分为三类:
UNPOOLED:不使用连接池的数据源
POOLED:使用连接池的数据源
JNDI:使用INDI实现的数据源
前两个数据源都实现javax.sql.DataSource接口
Mybatis框架是对JDBC的封装,所以Mypatis框架的事务控制方式,本身也是用JDBC的Connection对象的commit). rollback) .Connection对象的setAutoCommit()方法来设置事务提交方式的。自动提交和手工提交
该标签用于指定MyBatis所使用的事务管理器。MyBatis 支持两种事务管理器类型: JDBC 与MANAGED.
JDBC:使用DBC的事务管理机制,通过Connection对象的 commit(方法提交,通过rollback(方法回滚。默认情况下,mybatis将自动提交功能关闭了,改为了手动提交,观察日志可以看出,所以我们在程序中都需要自己提交事务或者回滚事务。
MANAGED:由容器来管理事务的整个生命周期(如Spring容器)。
SqlSessionFactory的openSession方法由重载,可以设置自动提交的方式。
如果sqlSession = sqlSessionFactory.openSession(true);参数设置为true,再次执行增删改的时候就不需要执行session.commit()方法,事务会自动提交。
观察下面的实体类,显然,我们这节的目的就是:
在查询时,将查询的球员信息映射给Player对象的属性;同时,将该球员所在球队的信息映射给Player对象种的Team。
public class Player{
private int playerId;
private String playerName;
private int playerNum;
private int teamId;
//关系字段:多个球员可以属于同意球队
//多方(球员)持有一方(球队)的对象
private Team team1;
private Team team2;
private Team team3;
...
}
public interface PlayerMapper {
Player queryById(int playerId);
Player queryById1(int playerId);
Player queryById2(int playerId);
Player queryById3(int playerId);
}
<resultMap id="baseResultMap" type="Player">
<id column="playerId" property="playerId"/>
<result column="playerName" property="playerName"/>
<result column="playerNum" property="playerNum"/>
<result column="teamId" property="teamId"/>
</resultMap>
那么,如何将球员所在球队的信息映射给Team呢
通过 对象.属性直接映射列名
要求:必须连接查询
一般会自定义结果映射
<resultMap id="joinTeamResult" type="Player" extends="baseResultMap">
<result column="teamId" property="team1.teamId"/>
<result column="teamName" property="team1.teamName"/>
<result column="location" property="team1.location"/>
<result column="createTime" property="team1.createTime"/>
</resultMap>
<select id="queryById1" resultMap="joinTeamResult">
SELECT * FROM `player` p INNER JOIN team t
on t.teamId=p.teamId
where playerId=#{id}
</select>
直接引用关联对象的Mapper映射
要求:必须使用连接查询
<resultMap id="joinTeamResult2" type="Player" extends="baseResultMap">
<association property="team2" javaType="Team" resultMap="com.zx.mapper.TeamMapper.baseResultMap"/>
</resultMap>
<select id="queryById2" resultMap="joinTeamResult2">
SELECT * FROM `player` p INNER JOIN team t
on t.teamId=p.teamId
where playerId=#{id}
</select>
使用关联对象的单独的查询语句的查询结果
要求: 不需要连接查询,需要关联对象中存在对应的查询语句
如下,joinTeamResult3映射需要一个Team对象作为映射,而queryById的查询结果刚好是一个Team对象,所以直接传输参数,引用queryById的查询结果。
<resultMap id="joinTeamResult3" type="Player" extends="baseResultMap">
<!--column:引用的关联对象中的查询所需要的参数-->
<association property="team3" javaType="Team" column="teamId"
select="com.zx.mapper.TeamMapper.queryById"/>
</resultMap>
<select id="queryById3" resultMap="joinTeamResult3">
SELECT * FROM `player` where playerId=#{id}
</select>
其实就是对一映射反过来,之前是站在球员的角度,多个球员属于一个球队。
现在就是站在球队的角度,一个球队可以有多个球员。
查询的需求就是,我们在查询出一个球队Team信息的时候,也能够将此球队所有的球员信息查询出来。
两种方式,所以在Team实体类中添加两个属性playerList、playerList2
public class Team {
private Integer teamId;
private String teamName;
private String location;
private Date createTime;
//关系字段:一个球队可以拥有多个球员
//一方(球队)持有多方(球员)的集合
private List<Player> playerList;
private List<Player> playerList2;
}
Team和数据库的字段映射关系
<resultMap id="baseResultMap" type="com.zx.pojo.Team">
<id column="teamId" property="teamId" javaType="java.lang.Integer"></id>
<result column="teamName" property="teamName" javaType="java.lang.String"></result>
<result column="location" property="location" javaType="java.lang.String"></result>
<result column="createTime" property="createTime" javaType="java.util.Date"></result>
</resultMap>
连接查询+引用关联对象的结果映射
<!--先创建引用关联对象的映射--> <resultMap id="joinPlayerMap1" type="Team" extends="baseResultMap"> <!--collection:对多的映射节点 property:实体类中要查询的集合属性 javaType:集合类型 ofType:集合中元素的类型 resultMap:引用的关联对象中结果映射(集合中的元素) --> <collection property="playerList" javaType="java.util.ArrayList" ofType="Player" resultMap="com.zx.mapper.PlayerMapper.baseResultMap"/> </resultMap> <select id="queryById1" resultMap="joinPlayerMap1"> select * from team t inner join player p on t.teamId=p.teamId where t.teamId=#{id} </select>
使用关联对象的单独的查询语句,它要求关联对象中有相应的查询语句
这里我们的映射需要的是球员信息,就需要关联对象中有查询结果为球员信息的查询语句。我们这里给它添加一个这样的查询:
PlayerMapper.xml:
<select id="queryByTeamId" resultMap="baseResultMap">
select * from player where teamId=#{i}
</select>
于是下面就可以直接借用球员信息的查询来映射team内部的playerList2的的属性
<resultMap id="joinPlayerMap2" type="Team" extends="baseResultMap">
<collection property="playerList2" javaType="arraylist" ofType="Player"
select="com.zx.mapper.PlayerMapper.queryByTeamId" column="teamId"/>
</resultMap>
<select id="queryById2" resultMap="joinPlayerMap2">
select * from team where teamId=#{id}
</select>
Mybatis中提供了完成动态sql的标签
查询条件参数类:
public class QueryTeamVo {
private String name;
private Date beginTime;
private Date endTime;
private String location;
}
<select id="queryByVo" parameterType="QueryTeamVo" resultMap="baseResultMap"> select * from team <where> <if test="name!=null"> teamName like concat(concat('%',#{name}),'%') </if> <if test="beginTime!=null"> and createTime>=#{beginTime} </if> <if test="endTime!=null"> and createTime<=#{endTime} </if> <if test="location!=null"> and location=#{location} </if> </where> </select>
<update id="update1" parameterType="Team">
update team
<set>
<if test="teamName!=null">
teamName=#{teamName},
</if>
<if test="location!=null">
location=#{location},
</if>
<if test="createTime!=null">
createTime=#{createTime},
</if>
</set>
where teamId=#{teamId}
</update>
<insert id="addList" parameterType="arraylist">
insert into team (teamName,location) values
<!-- collection:要遍历的集合:参数是集合类型,直接写list
item:便利的集合中的没有个数据
separator:将便利的结果用,分割
-->
<foreach collection="list" item="t" separator=",">
(#{t.teamName},#{t.location})
</foreach>
</insert>
<delete id="delList">
delete from team where teamId in
<!--collection:要遍历的集合,参数是集合类型,直接写list
item:便利的集合中的每一个数据
separator:遍历的结果用,分割
open="(" close=")":表示将遍历结果用open close包裹起来
-->
<foreach collection="list" item="teamId" separator="," open="(" close=")">
#{teamId}
</foreach>
</delete>
第一步引入依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
第二步在mybatis.xml中配置插件plugins(注意顺序)
<!--配置分页插件-->
<plugins>
<!--5.0版本之前使用的PageHelper,5.0之后使用PageInterceptor-->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--reasonable可以省略,分页合理化,默认false-->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
测试:注意由于mybatis要对查询语句进行拼接,这里的queryAll语句后面不要加分号
@Test public void testqueryByPage(){ //使用分页时PageHelper.startPage必须紧邻查询语句,而且只对它之后的第一条查询生效 PageHelper.startPage(2,2); List<Team> teamList = teamMapper.queryAll(); teamList.forEach(team -> System.out.println(team)); //插件提供了PageInfo类,可以获取分页信息 PageInfo<Team> info = new PageInfo<>(teamList); System.out.println("分页信息如下:"); System.out.println("总页数:"+info.getPages()); System.out.println("当前页是第"+info.getPageNum()+"页"); System.out.println("前一页是第"+info.getPrePage()+"页"); System.out.println("后一页是第"+info.getNextPage()+"页"); for(int num : info.getNavigatepageNums()){ System.out.println(num); } }
缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。将经常查询的数据存在缓存(内存)中,用户查询该数据的时候不需要
从磁盘(关系型数据库文件)上查询,而是直接从缓存中查询,提高查询效率,解决高并发问题。
MyBatis也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。
Mybatis的缓存结构体系:
在操作数据库时需要构造sqlSession对象,在对象中有一个(内存区域)数据结构 (HashMap) 用于存储缓存数据。不同的sqlSession之间的缓存数据区域
(HashMap)是互相不影响的。
一级缓存的作用域是同一 个SqlSession, 在同一个sqlSession中两次执行相同的sq|语句,第- -次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。
Mybatis默认开启一级缓存, 存在内存中(本地缓存)不能被关闭,可以调用clearCache()来清空本地缓存, 或者改变缓存的作用域。
工作原理图:
测试思路:开启日志,随便一个查询语句,连着查询两个,发现输出两次结果中间并没有日志打印,说明第二次查询并没有进行连接数据库等操作,而是直接从内存中拿的数据。
1、sqlsession.clearCache( );
2、execute update(增删改);
3、sqlsession.close( );
4、xml配置 flushCache=“true” ;
5、rollback;
6、commit.
多个SqlSession去操作同一个Mapper的sql语句, 多个SqlSession去操作数据库得到数据会存在二级缓存区域, 多个SqlSession可以共用二级缓存, 二级缓存是跨SqISession的。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace.
不同的sqlSession两次执行相同namespace下的sql语句参数相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
Mybatis默认没有开启二级缓存, 需要在setting全局参 数中配置开启级缓存。
如果缓存中有数据就不用从数据库中获取,大大提高系统性能。
二级缓存是mapper范围级别的,默认不启用
1、在Mybatis框架的全局配置文件中开启二级缓存
<settings>
<setting name="logImpl" value="LOG4J"/>
<!--是否开启二级缓存,默认false:不开启,true:开启-->
<setting name="cacheEnabled" value="true"/>
</settings>
2、在需要二级缓存的Mapper中添加缓存标志
3、实体类必须实现Serializable接口
4、测试
增删改会清空二级缓存、关闭连接会清空一级但不会清空二级缓存。
对于变化比较频繁的SQL,可以禁用二级缓存。
在开始了二级缓存的XML中对应的statement中设置useCache=false禁用当前Select语句的二级缓存,意味着该SQL语句每次只需都去查询数据库,不会查询缓存。
useCache默认值是true,对于一些很重要的数据尽量不放在二级缓存中。
示例:
<select id="queryById1" resultMap="joinPlayerMap1" useCache="false">
select * from team t inner join player p
on t.teamId=p.teamId where t.teamId=#{id}
</select>
<cache>
<property name="eviction" value="LRU"/><!--回收策略为LRU-->
<property name="flushInterval" value="60000" /><!--自动刷新时间间隔为605-->
<property name="size" value="1024"/><!--最多缓存1024个引用对象,如果超出,就使用回收策略回收-->
<property name="readonly" value="true"/><!--只读-->
</cache>
源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface CacheNamespace { Class<? extends Cache> implementation() default PerpetualCache.class; Class<? extends Cache> eviction() default LruCache.class; long flushInterval() default 0L; int size() default 1024; boolean readWrite() default true; boolean blocking() default false; Property[] properties() default {}; } /**属性介绍: 1.映射语句文件中的所有select语句将会被缓存: 2.映射语句文件中的所有CUD操作将会刷新缓存, 3.缓存会默认使用LRU (Least Recently Used) 算法来收回; 3.1、LRU -最近最少使用的:移除最长时间不被使用的对象。 3.2、FIFO -先进先出:按对象进入缓存的顺序来移除它们。 3.3、SOFT -软引用:移除基于垃圾回收器状态和软引用规则的对象。 3.4、WEAK -弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。 4.缓存会根据指定的时间间隔来刷新(默认情况下没有刷新间隔,缓存仅仅调用语句时刷新); 5.缓存会存储列表集合或对象(无论查询方法返回什么),默认存储1024个对象。 6.缓存会被视为是read/write (可读/可写)的缓存,意味着检索对象不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。 */
如果想在命名空间中共享其它命名空间的缓存配置,可以使用cache-ref元素来引用另一个缓存配置,例如:
<cache-ref namespace="com.zx.mapper.TeamMapper" />
//引用TeamMapper命名空间中的cache配置
在pom文件的build和plugins添加配置
<!--反向生成插件--> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.5</version> <configuration> <!--配置文件的路径--> <configurationFile>src/main/resources/generatorConfig.xml</configurationFile> <overwrite>true</overwrite> </configuration> <dependencies> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.5</version> </dependency> </dependencies> </plugin>
generatorConfig.xml的内容
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <!-- 配置生成器:标了序号的部分都需要修改为自己的内容--> <generatorConfiguration> <!--1、数据库驱动jar:添加自己的jar路径--> <classPathEntry location="D:\WorkSpace\Maven\MyRepository\mysql\mysql-connector-java\8.0.23\mysql-connector-java-8.0.23.jar" /> <context id="MyBatis" targetRuntime="MyBatis3"> <!--去除注释--> <commentGenerator> <property name="suppressAllComments" value="true" /> </commentGenerator> <!--2、数据库连接--> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mydb?usUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT" userId="root" password="123456"> </jdbcConnection> <!-- 默认false,把JDBC DECIMAL 和NUMERIC 类型解析为Integer; 为true 时把JDBC DECIMAL 和NUMERIC类型解析为java.math.BigDecimal --> <javaTypeResolver> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!--3、 生成实体类指定包名以及生成的地址 (可以自定义地址,但是路径不存在不会自动创建 使用Maven生成在target目录下,会自动创建) --> <javaModelGenerator targetPackage= "com.zx.pojo" targetProject="src\main\java"> <property name="trimStrings" value="true" /> </javaModelGenerator> <!--4、生SQLmapper.xml映射文件--> <sqlMapGenerator targetPackage="com.zx.mapper" targetProject="src\main\resources"> </sqlMapGenerator> <!--5、 生成Dao (Mapper)接口文件,--> <javaClientGenerator type="XMLMAPPER" targetPackage="com.zx.mapper" targetProject="src\main\java"> </javaClientGenerator> <!--6、 要生成哪些表(更改tableName和domainObjectName就可以) --> <!-- tableName:要生成的表名 enableCountByExample : Count语句中加入where条件查询,默认为true开启 enableUpdateByExample : Update语句中加入where条件查询,默以为true开启 enableDeleteByExample :Delete语句中加入where条件查询,默认为true开启 enableSelectByExample:Select多条语句中加入where条件查询,默认为true开启 selectByExampleQueryId:Select单个对象语句中加入where条件查询,默认为true开启 --> <table tableName= "stu" enableCountByExample="false" enableUpdateByExample="false" enableUpdateByPrimaryKey="false" enableDeleteByExample="false" enableDeleteByPrimaryKey="false" enableSelectByExample="false" selectByExampleQueryId="false"> <!--生成的字段区分大小写,符合java命名规范--> <property name="useActualColumnNames" value="true"/> </table> </context> </generatorConfiguration>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。