目录
XML配置形式用法
https://blog.csdn.net/hrnne/article/details/106034494
注解形式的用法
直接在接口文件中添加SQL语句
@Select
@Select("select * from Student where id = #{id}")
public Student selectStudentById(int id);
@Insert
@Update
@Delete
同上
多个参数的传递
通过学生年龄和性别来查询学生信息
select * from Student where Sage=? and Ssex=?
mybatis配置如下:
@Select(select * from student where age=#{age} and sex=#{sex})
public getStudentByAgeAndSex(int age, String sex);
XML可用的参数只有0,1,param1和Param2,Mybatis根据位置自定义的名字,可以将#{sex}改为#{0}或者#{param1}
多参数传递是给参数配置@Param注解
public Student getStudentByAgeAndSex(@Param("age") int age, @Param("sex") String sex);
当结果有多条记录时,加上List<>
public List<Student> getStudentByAgeAndSex(@Param("age") int age, @Param("sex") String sex);
配置注解后,mybatis自动的将参数封装成Map类型,@Param注解值作为Map中的key,参数值作为Map中的value,通过这种形式可以识别多参数的对应关系>
#{}和${}的区别
{}的使用
@Select("select * from Student where SID=#{sid}")
public Student getStudentBySID(int sid);
日志打印:
Preparing: select * from Student where SID=?
Parameters: 2(Integer)
#{}占位符在执行过程中将#{}替换成 ? 占位符,将参数值和SQL分开传递到服务器。
#{}使用类似于JDBC编程中preparedStatment。
${}的使用
参数的获取使用ognl表达式,使用的参数必须提供getteer方法
@Select("select * from Student where SID=${SID}")
public Student getStudentBySID(Student student);
打印日志:
Preparing: select * from Student where SID=1
Parameters:
使用${}方式传递参数时,直接将参数拼接到SQL。
${}使用JDBC编程中的Statement的操作。
#{}不存在SQL注入问题,采用预编译机制,将SQL和参数分别传递给SQL服务器,而${}存在SQL注入问题,所以一般情况时使用#{}。
动态SQL
Mybatis的强大特征之一 在于动态SQL,采用ognl表达式来处理SQL,根据表达式的不同,能将SQL进行拼接和组装。
主要的动态SQL标签:if,where,trim(where,set),foreach。
if标签
根据年龄和性别查询学生
<!--
if表达式一般放在where条件后,判断蚕食是否传递使用if test属性(必填)为true或false
test使用OGNL表达式处理,返回true则进入到if标签的SQL,否则不会进入
参数处理:
1)都不传:select * from student where !!!要添加1=1
2)都传:select * from student where age = #{age} sex = #{sex} !!!要加and
3)只传age:select * from student where 1=1 and age = #{age}
4)只传sex:select * from student where 1=1 and sex = #{sex}
-->
<select id="getStudentByAgeAndSex" parameterType="com.tl.pojo.Student" resultType="student">
select * from student where 1=1
<if test="age != 0 and age != null" >
and age = #{age}
</if>
<if test="sex != null" >
and sex = #{sex}
</if>
</select>
测试:
- 都传
- 只传age
- 只传sex
- 都不传
where标签
where标签:一般和if标签一块使用,如果标签包含的元素有返回值就插入where,将紧随where后面的And或OR开头的,将他们剔除
<!--where标签
1)只传age:select * from Student where and(会自动剔除) age = #{age}
2)都不传:select * from student
3)都传:select * from student where age = #{age} and sex = #{sex}
-->
<select id="getStudentByAgeAndSex" parameterType="com.tl.pojo.Student" resultType="student">
select * from student
<where>
<if test="age != 0 and age != null" >
and age = #{age}
</if>
<if test="sex != null" >
and sex = #{sex}
</if>
</where>
</select>
测试:与if相同。
trim标签(where/set)标签
trim标签用于取出SQL中多余的and关键字、逗号,使用在where或set中。
属性 | 描述 |
---|---|
prefix | 给SQL语句拼接的前缀 |
suffix | 给SQL语句拼接的后缀 |
prefixOverrides | 去除SQL语句前的关键字或字符 |
suffixOverrides | 去除SQL语句后的关键字或字符 |
<!--trim标签(结合where)
作用等同于直接使用where
-->
<select id="getStudentByAgeAndSex" parameterType="com.tl.pojo.Student" resultType="student">
select * from student
<trim prefix="where" prefixOverrides="and">
<if test="age != 0 and age != null" >
and age = #{age}
</if>
<if test="sex != null" >
and sex = #{sex}
</if>
</trim>
</select>
foreach标签
批量处理(插入,查询等)
场景:通过一批ID查询用户信息
select * from Student where id in(1,2,3)
insert into Student (id,name) values(20,“zhangsan”),(21,“lisi”),(22,“wangwu”)
接口文件的方法:
public List<Student> batchSelectStudentByIds(List<Integer> ids);
Mapper.xml文件
<!--批量查询SQL
foreach表达式:
collection(必填):指定输入参数类型。
list:列表。array:数组。map:map集合。
item:给集合中单个元素的名称
open:开始的字符串。close:结束的字符串。
separator:数据之间的分隔符。
-->
<select id="batchSelectStudentByIds" resultType="student">
select * from Student where sid in
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
测试:
模糊匹配
需求:查询Student表,表中含有"明”的所有用户
SQL:select * from Student where Sname like “%明%”;
方式1:直接在参数上拼接通配符
Mapper.xml配置:
测试用例:
日志打印:
方法2:Mysql中的concat(,)
concat(par1,par2) :进行字符串拼接。
Mapper.xml配置:
测试用例:
日志打印:
方法3:bind表达式处理
Mapper.xml配置:
接口方法:
测试用例:
日志打印:
动态代理:
原生的方法调用直接通过sqlSession方法调用:提供了selectOne,selectList,Insert,delete…方法
返回多个结果时,使用selectList,返回的结果不管是单个还是多个在resultType属性都是返回的Java对象全路径。返回单个结果对象使用selectOne。sqlSession.selectList(“com.tulun.dao.StudentMapper1.getStudentsByName”, “%L%”);
- 代理模式是Java中的一种设计模式。
- 代理模式中代理类和委托类具有相同的接口
- 代理类的主要职责是为委托类预处理消息,过滤消息等功能。
- 代理类的对象本身并不是真正的实现服务,而是通过委托类的对象的相- 关方法,来提供特定的一些服务。
- 代理类和委托类之间存在关联关系,一个代理类对象和一个委托类的对象相关联。
- 访问实际对象,是通过代理类方法的。
代理模式的话分为静态代理和动态代理。
- 静态代理是在程序编译阶段确定代理对象。
- 动态代理是在程序运行阶段确定代理对象
- 动态代理是在运行时根据Java代码指示动态生成的,相比较静态代理,优势在在于方便的对代理类的函数进行统一的处理,而不用修改每个代理类的方法。
Java中提供的动态代理方式有两种:JDK自带的动态代和CGLib实现代理。
JDK自带的代理方式
JDK自带的代理方式需要实现invocationHandler接口,实现invoke的方法。
- 提供一个接口类
- 提供一个委托类
- 实现动态代理,首先创建一个实现了InvocationHandler接口的辅助类。‘
public class UserProxy implements InvocationHandler {
private Object object;
public UserProxy(Object object){
this.object = object;
}
/**
* 实现动态代理,就需要实现InvocationHandler接口中的invoke方法,该方法有三个参数
* @param proxy :就是动态代理生成的代理对象
* @param method:就是调用的方法
* @param args:表示该方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理类特有!");
method.invoke(object,args);
System.out.println("代理类特有!");
return null;
}
}
- 使用产生代理对象时,需要调用代理辅助类,调用委托方法。
public static void main(String[] args) {
IUser iUser = (IUser) Proxy.newProxyInstance(UserProxy.class.getClassLoader(), new Class[]{IUser.class}, new UserProxy(new User()));
iUser.fun();
}
- 结果:
结果显示:当前的代理类调用fun方法,调用到代理辅助类UserProxy中的invoke方法,还调用到了委托类的fun实现。
JVM是如何自动实现invoke方法的调用呢?
CGLib实现代理
当代理没有接口类,此时Proxy和InvocationHandler机制不能使用了(JDK自带的代理模式的使用必须要有接口),此时可以使用CGLib库,采用底层字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截父类所有方法的调用,采用横切的逻辑。Spring AOP(横向切面)的技术技术就是使用动态代理。
引入依赖
<!--CGLib-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.2</version>
</dependency>
父类
public class Test {
public final void fun1(){//final修饰
System.out.println("父类功能1");
}
public void fun2(){
System.out.println("父类功能2");
}
}
辅助类
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理类");
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("代理类");
return o1;
}
}
测试类
public class MyCGLib {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//设置父类型
enhancer.setSuperclass(Test.class);
//创建增强对象
enhancer.setCallback(new MyMethodInterceptor());
Test test1 = (Test) enhancer.create();
test1.fun1();
System.out.println();
test1.fun2();
}
}
结果
只对非final方法进行了拦截。
Mybatis代理模式详解
Mybatis产生的代理是JDK自带的方式:仅在其中实现了接口。
mapper是如何添加进入的?
通过代码形式产生的会话工厂实例和读取配置形式是类似的。
代码形式中,Mapper接口是通过configuration.addMapper()形式来添加,参数为接口的class文件。
类似于:
Configuration configuration = new Configuration();
configuration.addMapper(StudentMapper1.class);//添加mapper
addMapper方法的实现
Configuration类中
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
//mapper实际上被添加到了MapperRegistry类中
}
MapperRegistry类
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {//只添加接口
if (hasMapper(type)) {//不允许接口重复添加
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
//将接口存放到HashMap中
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
执行的Configuration.addMapper()操作,最终被放入到HashMap中,其名为knownMappers,knownMappers是MapperRegistry类的属性,是一个HashMap对象,key为当前class对象,value为MapperProxyFactory实例。
从getMapper入手分析
SQLSession调用getMapper()方法操作实际调DefaultSqlSession类的实现
DefaultSqlSession类
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
//直接调用configuration中的getMapper方法
}
configuration类
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
//直接调用mapperRegistry中的getMapper方法
}
mapperRegistry类
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//class文件作为key获取到MapperProxyFactory类型的value值
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
//如果value值为空,抛出异常
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//(重点)通过这个操作返回了代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
调用SQLSession.getMapper操作,最终会到上面的这个方法,根据接口在HashMap中找到对应的Value值(MapperProxyFactory的对象),然后通过调用该对象newInstance的方法,获取到代理对象。
MapperProxyFactory类
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//JDK自带的代理方式生成映射器代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
MapperProxy类
public class MapperProxy<T> implements InvocationHandler, Serializable {
//当前类实现了InvocationHandler接口
//实现invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);//其中实现是调用委托类方法
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//使用缓存???
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行CRUD相关操作
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
总结
Mapper是如何注册的,Mapper接口是如何产生动态代理对象的?
答:Mapper接口在初始sqlSessionFactory的时候进行 Mapper接口注册,到了mapperRegistry类HashMap中,key是Mapper的class,value是当前的Mapper工厂。Mapper注册之后,可以从sqlSession中使用getMapper方法,使用了JDK自带的动态代理,产生目标接口Mapper的代理对象。
动态代理的代理辅助类是MapperProxy。
MyBatis缓存机制
缓存介绍
缓存提供了一种存储方式,当数据存储、访问数据时,可以直接访问缓存,减轻数据库的压力,提高数据库性能。
服务器和数据库是进行数据交互,无非是增删改查,查询操作比较多,查询操纵在数据库获取数据结果集,并不会引起数据本身的改变,增、删、改会改变数据库中的数据。
查询操作的特点:频率高,不会改变数据。
服务端到数据库是要进行网络交互。缓存可以减少服务端和数据库的交互,即减轻数据库压力,提高数据库的性能,缓存主要是用于查询缓存。
脏数据:
修改数据后查询到的数据和缓存中的旧数据一致。
为了避免脏数据问题,缓存更新时机如下:
查询操作:首先访问缓存,当缓存没有对应数据时,在数据库的中数据查询到后,插入到缓存中,后续的查询就可以直接访问缓存。
在变更操作时(修改,删除等)将数据变更到数据库时,直接将对应的缓存信息清空(hamp.remove(id))。
再次查询时,访问缓存,没有数据,就查询数据库将数据库返回的结果放入缓存。
通过该更新机制能够保证缓存数据是最新的数据。
缓存的实现方式:
1、在单实例服务器下,可以在当前服务代码中通过添加HashMap集合实例来充当缓存。
在集群服务中,本地缓存(HashMap)无法将缓存数据共享给其他的服务器。
2、在多服务器下,需要通过缓存共享的形式来缓存数据,使用缓存服务器处理。
缓存服务器的代表:Redis,memCache等
一级缓存
- 一级缓存是SqlSession级别的缓存,在操作数据库是需要构造SqlSession会话对象。
- 对同一个对象中的数据可以使用到缓存。
- 不同的SQLSession之间的缓存不同享。
Mybatis是默认支持一级缓存,不需要做相关配置。
case1:同一个sqlSession下连续的执行同一个查询操作。
@Test
public void selectStudentById(){
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper1 mapper = sqlSession.getMapper(com.tl.dao.StudentMapper1.class);
//第一次查询
Student student1 = mapper.selectStudentById(1);
//第二次查询
Student student2 = mapper.selectStudentById(1);
System.out.println(student1);
System.out.println(student2);
}
结果
在同一个SqlSession下,连续的查询同一个SQL语句,值查询一次数据库,第二次查询即访问缓存拿到结果。
case2:在同一个SqlSession下,先进行查询,在变更操作,然后进行同一个SQL查询。
@Test
public void selectStudentById(){
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper1 mapper = sqlSession.getMapper(com.tl.dao.StudentMapper1.class);
//第一次查询
Student student1 = mapper.selectStudentById(1);
System.out.println(student1);
//更新数据
mapper.updateStudentAgeById(1);
sqlSession.commit();
//第二次查询
Student student2 = mapper.selectStudentById(1);
System.out.println(student2);
}
结果
在第一次查询之后,会将存入数据缓存,进行了变更操作后,将缓存清空,第二次执行同一个SQL的查询操作时,缓存不存在了,就会继续查询数据库。
case3:不同的SqlSession下,同一个SQL查询操作。
@Test
public void selectStudentById(){
SqlSession sqlSession1 = sqlSessionFactory.openSession();
StudentMapper1 mapper1 = sqlSession1.getMapper(com.tl.dao.StudentMapper1.class);
//第一次查询
Student student1 = mapper1.selectStudentById(1);
System.out.println(student1);
SqlSession sqlSession2 = sqlSessionFactory.openSession();
StudentMapper1 mapper2 = sqlSession2.getMapper(com.tl.dao.StudentMapper1.class);
//第二次查询
Student student2 = mapper2.selectStudentById(1);
System.out.println(student2);
}
结果
在同一个SqlSession实例下,连续查询操作可以使用缓存,在不同的SqlSession实例下,缓存不共享。
二级缓存
- 二级缓存是Mapper级别的缓存,默认情况下二级缓存是关闭的。
- 同一个Mapper下不同的SqlSession可以共享二级缓存,范围更大。
- 不同Mapper缓存是相互隔离的。
使用步骤
注解形式
@CacheNamespace//开启二级缓存
public interface StudentMapper1 {
@Select("select * from Student where id = #{id}")
public Student selectStudentById(int id);
}
xml配置形式
- 需要在全局配置文件中打开二级缓存的开关。
<settings>
<!--开启二级缓存的开关-->
<setting name="cacheEnabled" value="true"/>
</settings>
- 在mapper.xml文件中添加cache标签
<cache></cache>
- 测试类
@Test
public void selectStudentById(){
SqlSession sqlSession1 = sqlSessionFactory.openSession();
StudentMapper1 mapper1 = sqlSession1.getMapper(com.tl.dao.StudentMapper1.class);
SqlSession sqlSession2 = sqlSessionFactory.openSession();
StudentMapper1 mapper2 = sqlSession2.getMapper(com.tl.dao.StudentMapper1.class);
//第一次查询
Student student1 = mapper1.selectStudentById(1);
sqlSession1.commit();
sqlSession1.close();
System.out.println(student1);
//第二次查询
Student student2 = mapper2.selectStudentById(1);
sqlSession2.commit();
sqlSession2.close();
System.out.println(student2);
}
- 结果
cache参数说明
<!--
cache参数说明:
flushInterval(缓存刷新间隔)单位毫秒,默认情况下不设置,在变更操作时,进行缓存刷新
size:(引用次数)记录缓存对象的数量 默认值1024
readOnly(只读)参数为true:false 表示缓存的数据对象实例是不能被修改
eviction:缓存失效的策略 默认LRU
LRU:最近最少使用的:移除最长时间不使用的
FIFO:先进先出,按照缓存对象的顺序来淘汰
SOFT:软引用
WEAK:弱引用
-->
<cache eviction="LRU" flushInterval="10000" size="12"></cache>
高级映射
在多表联合查询操作时,存在一对一、一对多和多对多的关系。
以订单数据模型为例
数据库表:
user:用户表:记录购买商品的用户信息
Orders:订单表,记录用户创建的所有订单
orderdetails:记录订单的详细的购买信息
items:商品表:记录商品信息
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`sex` char(1) DEFAULT NULL COMMENT '性别',
`address` varchar(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8
CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '下单用户id',
`number` varchar(32) NOT NULL COMMENT '订单号',
`createtime` datetime NOT NULL COMMENT '创建订单时间',
`note` varchar(100) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
KEY `FK_orders_1` (`user_id`),
CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8
//订单明细表
CREATE TABLE `orderdetail` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`orders_id` int(11) NOT NULL COMMENT '订单id',
`items_id` int(11) NOT NULL COMMENT '商品id',
`items_num` int(11) DEFAULT NULL COMMENT '商品购买数量',
PRIMARY KEY (`id`),
KEY `FK_orderdetail_1` (`orders_id`),
KEY `FK_orderdetail_2` (`items_id`),
CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
CREATE TABLE `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL COMMENT '商品名称',
`price` float(10,1) NOT NULL COMMENT '商品定价',
`detail` text COMMENT '商品描述',
`pic` varchar(64) DEFAULT NULL COMMENT '商品图片',
`createtime` datetime NOT NULL COMMENT '生产日期',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
测试数据
insert into `items`(`id`,`name`,`price`,`detail`,`pic`,`createtime`) values (1,'台式机',3000.0,'该电脑质量非常好!!!!',NULL,'2015-02-03 13:22:53'),(2,'笔记本',6000.0,'笔记本性能好,质量好!!!!!',NULL,'2015-02-09 13:22:57'),(3,'背包',200.0,'名牌背包,容量大质量好!!!!',NULL,'2015-02-06 13:23:02');
/*Data for the table `user` */
insert into `user`(`id`,`username`,`sex`,`address`) values (1,'王五','2',NULL),(10,'张三','1','北京市'),(16,'张小明','1','陕西西安'),(22,'陈小明','1','陕西西安'),(24,'张三丰','1','陕西西安'),(25,'陈小明','1','陕西西安'),(26,'王五',NULL,NULL);
/*Data for the table `orders` */
insert into `orders`(`id`,`user_id`,`number`,`createtime`,`note`) values (3,1,'1000010','2015-02-04 13:22:35',NULL),(4,1,'1000011','2015-02-03 13:22:41',NULL),(5,10,'1000012','2015-02-12 16:13:23',NULL);
/*Data for the table `orderdetail` */
insert into `orderdetail`(`id`,`orders_id`,`items_id`,`items_num`) values (1,3,1,1),(2,3,2,3),(3,4,3,4),(4,4,2,3);
数据库模型分析
一对一映射
需求:通过订单号查询订单及用户信息。
分析:订单号是在订单表中,通过订单表中的user_id字段可以查询到用户信息。
主表:orders 从表:user
SQL:select * from orders o,user u where o.user_id = u.id and o.number=?
在Orders类中添加user实例
private int id;
private int userId;
private long number;
private Date createtime;
private String note;
private User user;
//省略getter和setter方法
接口
public Orders getOrdersByNumber(String number);
mapper.xml配置文件
<select id="getOrdersByNumber" resultMap="OrderUserResultMap">
select o.*,u.id u_id,u.username u_username,u.sex u_sex,u.address u_address
from orders o,user u where o.user_id = u.id and o.number = #{number}
</select>
方法一:使用resultMap一一映射
<resultMap id="OrderUserResultMap" type="com.tl.pojo.Orders">
<result column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<result column="u_id" property="user.id"/>
<result column="u_username" property="user.username"/>
<result column="u_sex" property="user.sex"/>
<result column="u_address" property="user.address"/>
</resultMap>
方法二:使用resultMap提供的association配置一一对应
<resultMap id="OrderUserResultMap" type="com.tl.pojo.Orders">
<result column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!--
association:用于映射关联查询单个对象的信息
property:关联查询对应到自定义对象的属性
javaType:映射的java属性的全限定名
-->
<association property="user" columnPrefix="u_" javaType="user">
<result column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</association>
</resultMap>
方法三:使用extends属性继承当前resultMap
<resultMap id="OrderResultMap" type="orders">
<result column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
</resultMap>
<resultMap id="OrderUserResultMap" extends="OrderResultMap" type="orders">
<association property="user" columnPrefix="u_" javaType="user">
<result column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</association>
</resultMap>
方法四:使用association中的resultMap的属性(推荐)
<resultMap id="OrderResultMap" type="orders">
<result column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
</resultMap>
<resultMap id="UserResultMap" type="user">
<result column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</resultMap>
<resultMap id="OrderUserResultMap" extends="OrderResultMap" type="orders">
<association property="user" columnPrefix="u_" resultMap="UserResultMap"/>
</resultMap>
结果
一对多映射
需要关联映射是一个list结果。
需求:通过用户ID查询用户的订单信息。
分析:通过用户(主表:user)的关键信息查询所有相关的订单(从表:orders)。
SQL:select u.,o. from user u,orders o where u.id=o.user_id and u.id = ?
在User中添加一个Orders类型的集合
private int id;
private String username;
private String sex;
private String address;
private List<Orders> orders;
接口
public User getUserOrderById(int id);
mapper.xml配置文件
SQL片段的复用
<sql id = "UserProperty">
u.id,
u.username,
u.sex,
u.address
</sql>
<sql id="OrdersProperty">
o.id o_id,
o.user_id o_user_id,
o.number o_number,
o.createtime o_createtime,
o.note o_note
</sql>
<select id="getUserOrderById" resultMap="UserOrdersResultMap">
select <include refid="UserProperty"/>,
<include refid="OrdersProperty"/> from user u,orders o
where o.user_id = u.id and o.user_id = #{id}
</select>
<resultMap id="UserResultMap" type="user">
<result column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</resultMap>
<!--
使用resultMap中的Collection配置一对多关系
Collection:将关联查询的多条记录映射到集合兑现中
property:将关联查询的多条记录映射到的属性名:orders10s
ofType:执行映射的集合属性中的自定义类型
extends:继承主类性
-->
<resultMap id="UserOrdersResultMap" extends="UserResultMap" type="user">
<collection property="orders" columnPrefix="o_" ofType="orders">
<result column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
</collection>
</resultMap>
结果
ResultMap和ResultType的总结
resultType:
作用:将查询结果的SQL列名和pojo类的属性名完成一致性的映射。
缺点:若SQL结果列名和pojo类属性名不一致,则无法自动完成映射。
resultMap:
使用association和Collection完成一对一和一对多高级映射(对结果有特殊要求的映射对象)
- association:
作用:将关联查询的信息映射到一个pojo对象中
使用resultType无法将查询结果映射到pojo对象的pojo属性中时,选择resultMap来处理(association) - Collection:
将关联查询映射到一个List集合中
使用resultType无法将查询的结果映射到Pojo对象的List类型的pojo属性中时,使用resultMap的Collection标签来处理。
延时加载
懒加载:先从单表查询,需要时再从关联表去查询数据,这样能大大提高数据库的性能,因为单表查询要比多表查询快。association和Collection具备延时加载功能。
使用懒加载在全局配置XML文件的setting配置
<settings>
<!--懒加载配置-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!--先通过number在orders表中查询到user_id,再用这个user_id去user表中查询-->
<resultMap id="OrderUserResultMap1" extends="OrderResultMap" type="orders">
<association property="user" column="id={user_id}" select="com.tl.dao.OrdersMapper.getUserById"/>
<!--接口参数 select属性:关联查询的已存在方法的全路径-->
</resultMap>
<select id="getOrderByNumber" resultMap="OrderUserResultMap1">
select * from orders where number = #{number}
</select>
<select id="getUserById" resultType="user">
select * from User where id = #{id}
</select>
结果
逆向工程
Mybatis-Generator是Mybatis和ibatis的代码生成器,可以生成简单CRUD操作的xml配置文件,Mapper的接口文件(Mapper接口)、实体类(POJO),可以有效的减少开发者的工作量,减少写手动写SQL的过程,减少出错风险。
思想:需要先将数据库中表生成,包含字段,字段属性 可以来映射生成pojo类以及基本的Mapper的XML文件和Mapper接口文件。
Generator的官网:http://mybatis.org/generator/
Generator的使用步骤
引入依赖
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
mybatis-generator.xml文件
<!DOCTYPE generatorConfiguration PUBLIC
"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!--配置生成器-->
<generatorConfiguration>
<!--
context:生成一组对象环境
id:必填,上下文的ID,用于在生成错误时提示
targetRuntime:
Mybatis3:用于生成mybatis 3.0以上版本的内容,包含XXXBySample
Mybatis3Simple:类似于mybatis3,但是不生成XXXBySample
-->
<context id="simple" targetRuntime="MyBatis3Simple">
<!--
数据库连接的信息:驱动类,连接的地址,账号,密码
-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/product"
userId="root"
password="123456"
/>
<!--
生成pojo类信息
targetPackage:生成pojo类的包路径
targetProject:生成pojo类的位置
-->
<javaModelGenerator targetPackage="com.tl.pojo" targetProject="src/main/java">
<!--
enableSubPackages:是否生成schema作为包的后缀
-->
<property name="enableSubPackages" value="false" />
<!--
trimStrings:从数据库返回的值清理前后的空格
-->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--
生成Mapper.xml文件的配置信息
-->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/>
<!--
生成Mapper的接口文件的配置
-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.tl.dao" targetProject="src/main/java"/>
<!--指定数据库的表-->
<table tableName="orderdetail" />
</context>
</generatorConfiguration>
GeneratorDemo类
public class GeneratorDemo {
public static void main(String[] args) throws IOException, XMLParserException, InvalidConfigurationException, SQLException, InterruptedException {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
//读取配置文件路径
// File configFile = Resources.getResourceAsFile("mybatis-generator.xml");
File configFile = new File("F:\\java代码\\MyBatisTest\\src\\main\\resources/mybatis-generator.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}
}