MyBatis是一个apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了Google code,并改名为MyBatis,2013年11月迁移到GitHub。
MyBatis是一个实现了数据持久化的开源框架,就是对JDBC进行封装。
优点:
缺点:
核心接口和类
SqlSessionFactoryBuilder-build()->SqlSessionFactory-openSession()->SqlSession
原生方式
Mapper代理
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.jnfgroupId>
<artifactId>myBatis1artifactId>
<version>0.0.1-SNAPSHOTversion>
<name>myBatis1name>
<build/>
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.5version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.19version>
dependency>
dependencies>
project>
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver">property>
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?
useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai">property>
<property name="username" value="root">property>
<property name="password" value="12345">property>
dataSource>
environment>
environments>
configuration>
<mapper namespace="mapper.Mapper">
<insert id="save" parameterType="entity.Account">
insert into t_account(username,password,age) values(#{username},#{password},#{age})
insert>
mapper>
<mappers>
<mapper resource="mapper/Mapper.xml">mapper>
mappers>
public class Test1 {
public static void main(String[] args) {
//加载MyBatis的配置文件
InputStream inputStream=Test.class.getClassLoader().getResourceAsStream("config.xml");
//得到SqlSession三步
SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession=sqlSessionFactory.openSession();
//SQL语句位置
String statement="mapper.Mapper.save";
//创建实体类对象
Account account=new Account(1L,"张三","123123",22);
//执行插入和提交
sqlSession.insert(statement,account);
sqlSession.commit();
}
}
public interface AccountRepository {
public int save(Account account);
public int update(Account account);
public int deleteById(long id);
public List<Account> findAll();
public Account findById(long id);
}
<mapper namespace="repository.AccountRepository">
<insert id="save" parameterType="entity.Account">
insert into t_account(username,password,age)
values(#{username},#{password},#{age})
insert>
<update id="update" parameterType="entity.Account">
update t_account set username=#{username},password=#{password},
age=#{age} where id=#{id}
update>
<delete id="deleteById" parameterType="long">
delete from t_account where id=#{id}
delete>
<select id="findAll" resultType="entity.Account">
select * from t_account;
select>
<select id="findById" parameterType="long" resultType="entity.Account">
select * from t_account where id=#{id};
select>
mapper>
其中,mapper的namespace是接口的全类名
每个方法的id是方法名
parameterType是操作的参数类型
resultType是接口中对应方法的返回值类型(如果返回值是数据库中被影响的行数,如增删改,一定是int,此时可省略不写)
<mappers>
<mapper resource="mapper/Mapper.xml">mapper>
<mapper resource="repository/AccountRepository.xml">mapper>
mappers>
public class Test2 {
public static void main(String[] args) {
InputStream inputStream=Test.class.getClassLoader().getResourceAsStream("config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession=sqlSessionFactory.openSession();
//获取实现接口的代理对象
AccountRepository accountRepository=sqlSession.getMapper(AccountRepository.class);
//测试插入
Account account=new Account(6,"李四","12345",20);
accountRepository.save(account);
sqlSession.commit();
sqlSession.close();
}
}
一个学生只能属于一个班级,为一对一
use mybatis;
create table student(
id bigint primary key,
name varchar(11),
cid bigint
)
create table clazz(
id bigint primary key,
name varchar(11)
)
import lombok.Data;
@Data
public class Student {
private long id;
private String name;
private Clazz clazz;
public String toString() {
return "Student [id=" + id + ", name=" + name + ", clazz=" + clazz + "]";
}
}
import java.util.List;
import lombok.Data;
@Data
public class Clazz {
private long id;
private String name;
private List<Student> student;
public String toString() {
return "Clazz [id=" + id + ", name=" + name + "]";
}
}
public interface StudentRepository {
public Student findById(long id);
}
<mapper namespace="repository.StudentRepository">
<resultMap id="studentMap" type="entity.Student">
<id column="id" property="id">id>
<result column="name" property="name">result>
<association property="clazz" javaType="entity.Clazz">
<id column="cid" property="id">id>
<result column="cname" property="name">result>
association>
resultMap>
<select id="findById" parameterType="long" resultMap="studentMap">
select s.id,s.name,c.id as cid,c.name as cname from student s,clazz c
where s.id = #{id} and s.cid = c.id
select>
mapper>
<mapper resource="repository/StudentRepository.xml">mapper>
public class Test3 {
public static void main(String[] args) {
InputStream inputStream=Test.class.getClassLoader().getResourceAsStream("config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession=sqlSessionFactory.openSession();
//获取实现接口的代理对象
StudentRepository studentRepository=sqlSession.getMapper(StudentRepository.class);
System.out.println(studentRepository.findById(1));
sqlSession.commit();
sqlSession.close();
}
}
一个班级可以有多个学生,为一对多
相较一对一的改变是,在写SQL语句的结果映射中,用collection替代association,且其中有ofType属性表示集合内元素的类型:
<resultMap id="classesMap" type="entity.Clazz">
<id column="cid" property="id">id>
<result column="cname" property="name">result>
<collection property="students" ofType="entity.Student">
<id column="id" property="id"/>
<result column="name" property="name"/>
collection>
resultMap>
查询语句当然也变了:
<select id="findById" parameterType="long" resultMap="classesMap">
select s.id,s.name,c.id as cid,c.name as cname from student s,clazz c
where c.id = #{id} and s.cid = c.id
select>
一个顾客可以购买多种商品,一个商品也可以由多位顾客购买,是多对多关系
多对多的实质为一对多,传入顾客的id寻找顾客购买的所有货物(或传入货物的id寻找购买过的所有顾客),等同于传入班级id返回所有该班级的学生,因此其实就是一对多
MyBatis 框架需要:实体类、⾃定义 Mapper 接⼝、Mapper.xml
传统的开发中上述的三个组件需要开发者⼿动创建,逆向⼯程可以帮助开发者来⾃动创建三个组件,减
轻开发者的⼯作量,提⾼⼯作效率
使用:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.jnfgroupId>
<artifactId>myBatis1artifactId>
<version>0.0.1-SNAPSHOTversion>
<name>myBatis1name>
<build/>
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.5version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.19version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.18version>
dependency>
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.3.5version>
dependency>
dependencies>
project>
<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3">
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&
characterEncoding=utf-8&serverTimezone=Asia/Shanghai"
userId="root" password="12345">
jdbcConnection>
<javaModelGenerator targetPackage="entity"
targetProject="./src/main/java">
javaModelGenerator>
<sqlMapGenerator targetPackage="repository"
targetProject="./src/main/java">
sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER"
targetPackage="repository" targetProject="./src/main/java">
javaClientGenerator>
<table tableName="t_user" domainObjectName="User">table>
context>
generatorConfiguration>
public class GeneratorTest {
public static void main(String[] args) {
List<String> warings = new ArrayList<String>();
boolean overwrite = true;
String genCig = "/generatorConfig.xml";
File configFile = new File(GeneratorTest.class.getResource(genCig).getFile());
ConfigurationParser configurationParser = new ConfigurationParser(warings);
Configuration configuration = null;
try {
configuration = configurationParser.parseConfiguration(configFile);
} catch (IOException e) {
e.printStackTrace();
} catch (XMLParserException e) {
e.printStackTrace();
}
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = null;
try {
myBatisGenerator = new MyBatisGenerator(configuration, callback, warings);
} catch (InvalidConfigurationException e) {
e.printStackTrace();
}
try {
myBatisGenerator.generate(null);
} catch (SQLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行后,将自动生成实体类、Mapper接口、Mapper.xml
上面举的例子,使用学生的id到两张表中查询数据,无论最后是否需要班级的信息,都要查询两张表。
延迟加载可以在特定的情况下访问特定的数据库,在其他情况下可以不访问某些表,在一定程度上减少了java应用与数据库的交互次数。
使用:
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
<setting name="lazyLoadingEnabled" value="true" />
settings>
public Student findByIdLazy(long id);
public Classes findByIdLazy(long id);
<resultMap id="studentMapLazy" type="entity.Student">
<id column="id" property="id">id>
<result column="name" property="name">result>
<association property="clazz" javaType="entity.Clazz"
select="repository.ClassesRepository.findByIdLazy" column="cid">
association>
resultMap>
<select id="findByIdLazy" parameterType="long" resultMap="studentMapLazy">
select * from student where id = #{id}
select>
<select id="findByIdLazy" parameterType="long" resultType="com.southwind.entity.Classes">
select * from classes where id = #{id}
select>
使用缓存可以减少java应用与数据库的交互次数。
比如查询一个对象,第一次查询后将查询到的对象保存到缓存中,当下一次查询时直接取出即可。但如果该对象被改变,需要同时也清空缓存,避免查询到之前的信息。
一级缓存:SqlSession级别,默认开启,且不能关闭。
同一个SqlSession执行两次相同的SQL语句时,第二次查询直接从缓存中获取;而不同的SqlSession之间缓存的数据区域互不影响。
二级缓存:Mapper级别,默认关闭,可以开启。
二级缓存是跨SqlSession的,作用域是Mapper的同一个namespace,不同的Sql两次执行相同namespace下的SQL语句,第二次直接从二级缓存中获取数据。
二级缓存可以使用MyBatis自带的二级缓存,也可以用ehcache(一个缓存框架)
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
<cache>cache>
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account implements Serializable {
private long id;
private String username;
private String password;
private int age;
}
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.0.0version>
dependency>
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcache-coreartifactId>
<version>2.4.3version>
dependency>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
defaultCache>
ehcache>
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<property name="timeToIdleSeconds" value="3600"/>
<property name="timeToLiveSeconds" value="3600"/>
<property name="memoryStoreEvictionPolicy" value="LRU"/>
cache>
实体类不需要实现序列化接口。
假设有表:
id | name | age |
---|---|---|
006 | 张三 | 22 |
008 | 李四 | 20 |
使用三个条件查询:
select * from student where id=#{id} and name= #{name} and age=#{age}
能正确查出来。如果只使用两个条件:
select * from student where id=#{id} and name= #{name}
照理说能够查出,但实际情况是这句话会变成
select * from student where id=#{id} and name= #{name} and age=null
而得到空结果。
或者在数据插入时,如果只有部分属性发生改变,重写所有会带来资源浪费。此时都需要使用动态SQL。
where+if
:一个 if 标签里写一个条件,考虑到如果第一个标签不在SQL语句会出错,用 where 就能判断关键字是否需要加上
<select id="findByAccount" parameterType="entity.Account"
resultType="entity.Account">
select * from t_account
<where>
<if test="id!=0">
id = #{id}
if>
<if test="username!=null">
and username = #{username}
if>
<if test="password!=null">
and password = #{password}
if>
<if test="age!=0">
and age = #{age}
if>
where>
select>
where+choose+when
<select id="findByAccount" parameterType="entity.Account"
resultType="entity.Account">
select * from t_account
<where>
<choose>
<when test="id!=0">
id = #{id}
when>
<when test="username!=null">
username = #{username}
when>
<when test="password!=null">
password = #{password}
when>
<when test="age!=0">
age = #{age}
when>
choose>
where>
select>
trim
trim 标签中的 prefix 和 suffix 属性会被⽤于⽣成实际的 SQL 语句,会和标签内部的语句进⾏拼接,如果语句前后出现了 prefixOverrides 或者suffixOverrides 属性中指定的值,MyBatis 框架会⾃动将其删除。
<select id="findByAccount" parameterType="entity.Account"
resultType="entity.Account">
select * from t_account
<trim prefix="where" prefixOverrides="and">
<if test="id!=0">
id = #{id}
if>
<if test="username!=null">
and username = #{username}
if>
<if test="password!=null">
and password = #{password}
if>
<if test="age!=0">
and age = #{age}
if>
trim>
select>
set+if
用于update语句
<update id="update" parameterType="entity.Account">
update t_account
<set>
<if test="username!=null">
username = #{username},
if>
<if test="password!=null">
password = #{password},
if>
<if test="age!=0">
age = #{age}
if>
set>
where id = #{id}
update>
foreach
可以迭代生成一系列值,主要用于SQL的in语句
<select id="findByIds" parameterType="entity.Account"
resultType="entity.Account">
select * from t_account
<where>
<foreach collection="ids" open="id in (" close=")" item="id" separator=",">
#{id}
foreach>
where>
select>