基础知识
对原生JDBC程序问题总结
- 数据库连接,在使用时立即创建,用完即释放,对数据库进行频繁的连接开启和关闭,造成数据库资源浪费,影响数据库性能。
解决方案:使用数据库连接池管理数据库连接。
- SQL语句硬编码到Java程序中,如果SQL语句需要修改,需要重新编译Java代码,不利于系统维护。
解决方案:将SQL语句配置在XML配置文件中,即使SQL变化,不需要对JAVA代码进行重新编译。
- 向preparedStatement中设置参数,对占位符号位置和设置参数值硬编码在Java代码中,不利于系统维护。
解决方案:将SQL语句及占位符和参数都配置在XML配置文件中。
- 从resultSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,不利于系统维护。
解决方案:将查询的结果集自动映射为Java的POJO对象。
Mybatis框架原理
Mybatis是什么?
- Mybatis是一个持久化层的框架,是Apache旗下的顶级项目。
- Mybatis让程序将主要精力放在SQL上,通过Mybatis提供的映射方式,自由灵活生成(半自动化)满足需要的SQL语句。
- Mybatis可以将输入参数自动进行输入映射,将结果集自动映射为Java的POJO对象(输出映射)。
Mybatis框架
- SqlMapConfig.xml(Mybatis的全局配置文件)
配置数据源、事务等Mybatis运行环境
配置映射文件(在mapper.xml中配置SQL语句)
- SqlSessionFactory(会话工厂)
作用:创建会话sqlSession
- SqlSession(会话)
作用:操作数据库(发出SQL,增、删、改、查)
- Executor(执行器)
作用:SqlSession内部通过执行器操作数据库
- mapped statement(底层封装对象)
作用: 对操作数据库存储封装,包括SQL语句、输入参数、输出结果类型。
- MySQL数据库
Mybatis入门程序
SqlMapConfig.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">
<configuration>
<!-- 和Spring整合后就无需environments -->
<environments>
< environment>
<!-- 使用JDBC事务管理,事务由Mybatis控制 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池,由Mybatis管理 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/..." />
<property name="username" value="root" />
<property name="password" value="admin123" />
</dataSource>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
<mapper resource="sqlMap/User.xml" />
</mappers>
</configuration>
根据Id查询用户-映射文件(User.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命名空间,作用就是对SQL进行分类话管理,理解SQL隔离
注意:使用mapper代理方法开发,namespace必须等于mapper接口的全路径。
-->
<mapper namespace="test">
<!-- 在映射文件中可以配置很多SQL语句 -->
<!-- 需求:通过Id查询用户表的记录 -->
<!--
通过select执行数据库查询
id:标识映射文件中的SQL
将SQL语句封装到mappedStatement对象中,所以将Id称为statement的Id
parameterType:指定输入参数的类型。
#{}:表示一个占位符号
#{id}:其中Id表示接收的输入参数,参数名称就是Id,
如果输入参数是简单类型,#{}中的参数名可以任意。
resultType:指定SQL输出结果的单条记录映射的Java对象。
-->
<select id="findUserById" parameterType="int" resultType=“xxx.xxx.UserPO”>
SELECT *
FROM USER
WHERE id=#{id}
</select>
<!--
根据用户名称模糊查询用户信息,可能返回多条值。
resultType:指定的就是单条记录所映射的Java对象类型。
${}:表示拼接sql串,将接收到的参数内容不加任何修饰拼接在sql中。
安全隐患:使用${}会导致SQL注入
${value}:接收输入参数的内容,如果传入的参数是简单类型,${}中只能使用value。
-->
<select id="findUserByName" parameterType="java.lang.String" resultType="xxx.xxx.UserPO">
SELECT *
FROM USER
WHERE username like '%${value}%'
</select>
<!-- 添加用户 -->
<!--
parameterType:指定输入参数类型为POJO类。
#{attribute}:指定POJO类中的属性字段,Mybatis通过OGNL技术获取对象的属性值。
-->
<insert id="insertUser" parameterType="xxx.xxx.UserPO">
<!--
一:自增主键
将插入数据的主键返回到user对象中
SELECT LAST_INSERT_ID():得到刚插入数据记录的主键值,只适用于自增主键。
keyProperty:将查询到的主键值设置到parameterType指定的对象的特定属性中。
resultType:指定SELECT LAST_INSERT_ID()的结果类型。
order:SELECT LAST_INSERT_ID()执行顺序,相对于insert语句的顺序。
-->
<selectKey keyProperty="id" resultType="java.lang.Integer" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
<!--
二:非自增主键
使用MySQL的uuid()生成主键。
执行过程:
首先通过uuid()生成主键值,将主键值设置到user对象的id中;
其次在insert执行时,从user对象中取出id属性值。
-->
<selectKey keyProperty="id" resultType="java.lang.String" order="BEFORE">
SELECT uuid()
</selectKey>
INSERT INTO user(id, username, birthday, gender, address)
VALUES(#{id}, #{username}, #{birthday}, #{gender}, #{address})
</insert>
<!-- 删除用户 -->
<!--
根据id删除用户,输入参数为id。
-->
<delete id="deleteUser" parameterType="java.lang.Integer">
DELETE FROM USER
WHERE id=#{id}
</delete>
<!-- 更新用户 -->
<!--
需要输入更新的UserPO对象,必须包括要更新user的id值。
-->
<update id="updateUser" parameterType="xxx.xxx.UserPO">
UPDATE USER
SET username=#{username}, birthday=#{birthday}, gender=#{gender}, address=#{address}
WHERE id=#{id}
</update>
</mapper>
Mybatis入门程序
public class Mybatis {
/**
*根据ID查询用户信息,得到一条记录结果。
*/
@Test
public void findUserByIdTest() throws IOException {
//Mybatis配置文件
String resource = "SqlMapConfig.xml";
//得到配置文件流
InputStream inputStream = new InputStream(resource);
//创建回话工厂,传入Mybatis的配置文件信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过sqlSession操作数据库
//第一个参数:映射文件中的statement的id,等于namespace+"."+statement的id。
//第二个参数:指定和映射文件中所匹配的parameterType类型的参数
//sqlSession.selectOne的结果是与映射文件中所匹配的resultType类型的对象。
User user = sqlSession.selectOne("test.findUserById", 1);
//关闭会话
sqlSession.close();
}
/**
*通过名字模糊查询用户信息列表
*/
@Test
public void findUserByNameTest() throws IOException {
//Mybatis配置文件
String resource = "SqlMapConfig.xml";
//得到配置文件流
InputStream inputStream = new InputStream(resource);
//创建回话工厂,传入Mybatis的配置文件信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//users中的user和映射文件中resultType所指定的类型抑制。
List<User> users = sqlSession.selectList("test.findUserByName", "小明");
//关闭会话
sqlSession.close();
}
/**
*添加用户
*/
@Test
public void insertUserTest() throws IOException {
//Mybatis配置文件
String resource = "SqlMapConfig.xml";
//得到配置文件流
InputStream inputStream = new InputStream(resource);
//创建回话工厂,传入Mybatis的配置文件信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//插入对象
User user = new User(10001, "Jack", new Date(), 1, "浙江杭州");
sqlSession.insert("test.insertUser", user);
//提交事务
sqlSession.commit();
//关闭会话
sqlSession.close();
}
/**
*根据id删除用户
*/
@Test
public void deleteUserTest() throws IOException {
//Mybatis配置文件
String resource = "SqlMapConfig.xml";
//得到配置文件流
InputStream inputStream = new InputStream(resource);
//创建回话工厂,传入Mybatis的配置文件信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//根据id删除用户
int id = 10001;
sqlSession.insert("test.insertUser", id);
//提交事务
sqlSession.commit();
//关闭会话
sqlSession.close();
}
/**
*更新用户信息
*/
@Test
public void updateUserTest() throws IOException {
//Mybatis配置文件
String resource = "SqlMapConfig.xml";
//得到配置文件流
InputStream inputStream = new InputStream(resource);
//创建回话工厂,传入Mybatis的配置文件信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//更新用户信息,id必须存在。
int id = 10001;
User user = new User(id, "Rose", new Date(), 2, "浙江杭州");
sqlSession.insert("test.insertUser", user);
//提交事务
sqlSession.commit();
//关闭会话
sqlSession.close();
}
}
小结:
- #{}
#{}表示一个占位符,接收的输入参数类型可以是简单类型、POJO、HashMap等。如果接收简单类型,#{}中可以写成value或其他任意名字。如果接收POJO对象,通过OGNL技术读取对象中的属性值,如:user.username、order.user.username。
- ${}
${}表示一个拼接符号,会引入SQL注入,不建议使用。接收的输入参数类型可以是简单类型、POJO、HashMap等。如果接收简单类型,${}中只能写成value。
Mybatis与Hibernate本质区别和应用场景
Hibernate:是一个标准的ORM(对象关系映射)框架,入门门槛较高,不需要编写SQL语句,Hibernate会会自动生成,因此对SQL语句进行修改和优化比较困难。
应用场景:使用需求变化小的中小型项目,如后台管理系统、ERP、ORM和OA等。
Mybatis:专注于SQL语句本身,需要工程师自己编写SQL语句,对SQL的修改和优化比较方便。Mybatis是一个不完全的ORM框架,虽然需要工程师自己写SQL语句,但Mybatis实现了POJO的映射(输入映射、输出映射)。
应用场景:适用于需求变化较大的项目,如互联网项目。
企业进行技术选型,以低成本、高回报作为原则,根据项目组的技术成熟度进行选择。
Mybatis开发DAO的两种方式:
SqlSession使用范围
SqlSessionFactoryBuilder
通过SqlSessionFactory创建SqlSessionFactory
SqlSessionFactory
通过SqlSessionFactory创建SqlSession,使用单例模式管理sqlSessionFactory。如果Mybatis与Spring整合,则Spring创建的bean默认为单例。
SqlSession
SqlSession是面向用户的接口,但其实现类是线程不安全的,在SqlSession实现类中除了有接口中的方法(操作数据库的方法),还存在数据域属性。所以SqlSession最佳应用场合在方法体内,定义成局部变量使用。
原生DAO开发(程序需要编写DAO接口和DAO实现类)(掌握)
实现步骤:
- 定义DAO接口,编写DAO实现类。
- 在DAO实现类中注入SqlSessionFactory,通过SqlSessionFactory实例在方法体内创建SqlSession。
具体实现:
DAO接口
public interface UserDAO {
/**
*根据id查询用户信息
*/
UserPO findUserById(int id) throws Exception;
/**
*根据名称模糊查询用户信息
*/
List<UserPO> findUserByName(String name) throws Exception;
/**
*添加用户信息
*/
UserOP insertUser(UserPO user) throws Exception;
/**
*删除用户信息
*/
void deleteUser(int id) throws Exception;
}
DAO接口实现类
public class UserDAOImpl implement UserDAO {
//通过构造函数注入
private SqlSessionFactory sqlSessionFactory;
@AutoWired
public UserDAOImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
/**
*根据id查询用户信息
*/
public UserPO findUserById(int id) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession.selectOne("test.findUserById", id);
}
/**
*根据名称模糊查询用户信息
*/
public List<UserPO> findUserByName(String name) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession.selectList("test.findUserByName", name);
}
/**
*添加用户信息
*/
public UserPO insertUser(UserPO user) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//主键自动会写
sqlSession.insert("test.insertUser", user);
sqlSession.commit();
sqlSession.close();
return user;
}
/**
*删除用户信息
*/
public void deleteUser(int id) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.delete("test.deleteUser", id);
sqlSession.commit();
sqlSession.close();
}
}
原始DAO存在的问题
- DAO接口实现类方法中存在大量的模版方法。
- 调用sqlSession方法时将statement的id硬编码了。
- sqlSession方法输入参数使用的是范型,当输入参数有误时,编译阶段不会报错,不利于程序开发。
Mybatis的Mapper接口(相当于DAO接口)代理开发(掌握)
实现步骤:
- 编写UserMapper.xml映射文件。
- 编写UserMapper接口(相当于DAO接口)需要遵循一些开发规范,Mybatis可以自动生成UserMapper接口的实现类代理。
开发规范:
- 在UserMapper.xml中,namespace属性值要等于UserMapper接口的全路径。
- 在UserMapper.java中,接口的方法名要与UserMapper.xml中statement的id保持一致。
- 在UserMapper.java中,接口的方法的输入参数类型要与对应的statement指定的parameterType参数类型一致。
- 在UserMapper.java中,接口的方法的返回值类型要与对应的statement指定的resultType结果类型一致
具体实现:
UserMapper.java DAO层接口
public interface UserMapper {
/**
*根据id查询用户信息
*/
UserPO findUserById(int id) throws Exception;
/**
*根据名称模糊查询用户信息
*/
List<UserPO> findUserByName(String name) throws Exception;
}
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命名空间,作用就是对SQL进行分类话管理,理解SQL隔离
注意:使用mapper代理方法开发,namespace必须等于mapper接口的全路径。
-->
<mapper namespace="test">
<select id="findUserById" parameterType="int" resultType=“xxx.xxx.UserPO”>
SELECT *
FROM USER
WHERE id=#{id}
</select>
<select id="findUserByName" parameterType="java.lang.String" resultType="xxx.xxx.UserPO">
SELECT *
FROM USER
WHERE username like '%${value}%'
</select>
</mapper>
SqlMapConfig.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">
<configuration>
<!-- 和Spring整合后就无需environments -->
<environments>
......
</environments>
<!-- 加载映射文件 -->
<mappers>
<mapper resource="sqlMap/User.xml" />
<mapper resource="mapper/UserMapper.xml" />
</mappers>
</configuration>
使用测试
public class Mybatis {
/**
*根据id查询用户信息
*/
@Test
public void findUserByIdTest() {
//Mybatis配置文件
String resource = "SqlMapConfig.xml";
//得到配置文件流
InputStream inputStream = new InputStream(resource);
//创建回话工厂,传入Mybatis的配置文件信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建UserMapper实例,Mybatis自动生成UserMapper接口的代理对象。
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int id = 10001;
UserPO user = userMapper.findUserById(id);
sqlSession.close;
}
/**
*根据名称模糊查询用户信息
*/
@Test
public void findUserByNameTest() {
//Mybatis配置文件
String resource = "SqlMapConfig.xml";
//得到配置文件流
InputStream inputStream = new InputStream(resource);
//创建回话工厂,传入Mybatis的配置文件信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建UserMapper实例,Mybatis自动生成UserMapper接口的代理对象。
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
String name = “Jack”;
List<UserPO> users = userMapper.findUserByName(name);
sqlSession.close;
}
}
小结
代理对象内部调用selectOne和selectList方法
如果Mapper接口方法返回单个POJO对象,代理对象内部通过selectOne方法查询数据库。
如果Mapper接口方法返回集合对象,代理对象内部通过selectList方法查询数据库。
Mapper接口的方法只能有一个输入参数
可以使用POJO或者包装类型的POJO满足不同业务方法的需求。
注意:DAO层可以使用包装类型的POJO作为输入参数,但service业务层最好不要用包装类型的POJO,不利于业务层的扩展。
Mybatis配置文件SqlMapConfig.xml
Mybatis全局配置文件SqlMapConfig.xml的配置内容如下:
properties(属性)
Mybatis按照如下顺序加载属性
-
在properties元素体内定义的属性首先被读取
-
然后会读取properties元素中通过resource或url加载的属性,覆盖以读取的同名属性
-
最后读取parameterType传递的属性,覆盖以读取的同名属性
因此,通过parameterType传递的属性优先级最高,resource和url加载的属性次之,properties元素体内的属性优先级最低。
建议:将属性定义在properties属性文件中,且属性文件中的属性名要有一定的特殊性,以免被优先级高的同名属性覆盖。如jbdc.properties属性文件,其中属性名文jdbc.username、jdbc.password。
settings(全局配置参数)
Mybatis框架运行时配置的一些参数,如二级缓存、延迟加载等控制参数。
typeAliases(类型别名)
针对parameterType和resultType指定的全路径的类型定义别名,简化书写。
<typeAliases>
<!--
单个别名定义
type:类型全路径
alisa:别名
-->
<typeAlias type="xxx.xxx.UserPO" alias="UserPO" />
<!--
批量别名定义
自定包名,myBatis自动扫描包中的类,自动定义别名,别名就是类名(首字母大写或小写)
-->
<package name="xxx.xxx" />
</typeAliases>
typeHandlers(类型处理器)
在Mybatis中,通过typeHandlers完成JDBC与Java之间的类型转换。
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
<mappers>
<!-- 通过resource属性一次加载一个映射文件 -->
<mapper resource="mapper/UserMapper.xml" />
<!--
规约:需要将XxMapper.java接口类名与XxMapper.xml映射文件名称保持一致,且在同一个目录中。(前提:Mapper接口方式)
-->
<mapper class="xxx.xxx.UserMapper" />
<!--
批量加载mapper
指定mapper接口的包名,Mybatis自动扫描包下面的所有mapper接口。
同样需要遵守上述“规约”。
-->
<package name="xxx.xxx" />
</mappers>
Mybatis核心:
Mybatis输入映射(掌握)
通过parameterType指定输入参数的类型,类型可以是简单类型、POJO、HashMap和POJO的包装类型。
/**
*包装类型的POJO
*/
public class UserQueryPO {
private UserPO user;
private OrderPO order;
//GET、SET方法省略
}
<mapper>
<!-- 综合查询 -->
<select id="findOrder" parameterType="UserQueryPO" resultType="Order">
SELECT *
FROM ORDER as o
WHERE
o.userId = #{user.id} and o.id = #{order.id}
</select>
</mapper>
Mybatis输出映射(掌握)
使用resultType时,如果SQL语句返回结果与resultType的POJO类型中存在一个以上的属性字段名称相同,就能映射成功,其他没有对应属性的字段为默认值。在返回结果是单个数据是,resultType才能指定为简单类型。
Mybatis中使用resultMap完成高级输出结果映射。
如果查询出来的列名与POJO的属性不一致,又想映射到POJO属性上,通过定义一个resultMap对列名和POJO属性名之间做一个映射关系。
<mapper>
<!-- 1.定义resultMap -->
<resultMap type="user" id="userResultMap">
<!--
id表示结果集中的唯一标识
column:查询出来的列名
property:POJO中的属性名
-->
<id column="_id" property="id"/>
<!-- result:对普通列名映射定义 -->
<result column="name" property="username" />
</resultMap>
<select id="findUserByName" parameterType="java.lang.String" resultMap="userResultMap">
SELECT *
FROM USER as u
WHRER
u.username = #{name}
</select>
</mapper>
Mybatis的动态SQL(掌握)
Mybatis核心就是对SQL语句进行灵活操作,通过表达式进行判断,对SQL进行灵活拼接,组装。
<select id="findUserByName" parameterType="UserPO" resultType="UserPO">
SELECT *
FROM USER as u
<!-- where可以自动去掉条件中的第一个ADN -->
<where>
<if test="user.username != null">
AND u.username = #{username}
</if>
<if test="...">
<if test="...">
AND ...
</if>
</if>
</where>
</select>
SQL片段:将通用的SQL语句定义成SQL片段,其他statement中就可以引用该SQL片段。
<mapper>
<!--
定义SQL片段
id:SQL片段的唯一标识
经验:SQL片段是基于单表定义的,这样SQL片段可重用性高。
在SQL片段中不要包含where标签。
-->
<sql id="query_user_where">
<if test="user.username != null">
AND u.username = #{username}
</if>
<if test="...">
<if test="...">
AND ...
</if>
</if>
</if>
</sql>
<select id="findUserByName" parameterType="UserPO" resultType="UserPO">
SELECT *
FROM USER as u
<where>
<!-- 引用SQL片段id,如果refid指定的SQL片段id不在该mapper文件中,需要加上对应mapper文件的namespace(命名空间)的值 -->
<include refid="query_user_where"/>
</where>
</select>
</mapper>
foreach:向SQL传递数组或List,Mybatis使用foreach解析。
<mapper>
<select id="..." parameterType="UserPO" resultType="UserPO">
SELECT * FROM USER AS u
<where>
<if test="ids != null">
<!-- AND u.id IN(10,32,76) -->
AND u.id IN
<foreach collection="ids" item="id" open="(", close=")" separator=",">
#{id}
</foreach>
</if>
</where>
<where>
SELECT * FROM USER AS u
<if test="ids != null">
<!-- AND (u.id=10 OR u.id=32 OR u.id=76) -->
AND
<foreach collection="ids" item="id" open="(" close=")" separator="OR">
u.id=#{id}
</foreach>
</id>
</where>
</select>
</mapper>
高级知识
订单商品数据模型分析
分析数据模型的思路
- 每张表记录的数据内容:分模块对每张表记录的内容进行熟悉,相当于学习系统功能的过程。
- 每张表重要字段的设置:非空字段、外键字段。
- 数据库级别表与表之间的关系:外键关系。
- 表与表之间的业务关系:建立在某个业务基础上分析表与表之间的业务关系。
数据模型的分析
用户表user:购买商品的用户信息。(字段:id、username、)
id:自增主键。
订单表order:用户创建的订单。(字段:id、user_id、number、createtime、note)
id:自增主键。
number:订单号。
user_id:外键。order表为子表,user表为父表。
订单明细表orderdetail:订单详明细。(字段:id、order_id、item_id、item_num)
id:自增主键
order_id:外键。orderdetail表为子表,order表为父表。
item_id:外键。orderdetail表为子表,item表为父表。
商品信息表item:商品详细信息。(字段:id、name、price、detail、pic、createtime)
id:自增主键。
首先分析数据库级别存在外键关系的表与表之间的业务关系:
order外键:user_id
user--->order:一对多,一个用户可以创建多个订单。
order--->user:一对一,一个订单只能由一个用户创建。
orderdetail外键:order_id
order--->orderdetail:一对多,一个订单可以包含多个订单明细,一个订单可以包含多个商品,一个商品信息记录在一个订单明细中。
orderdetail--->order:一对一,一个订单明细只能存在于一个订单中。
orderdetail外键:item_id
orderdetail--->item:一对一,一个订单明细中只包含一个商品信息。
item--->orderdetail:一对多,一个商品可以包含在多个订单明细中。
其次分析数据库级别没有关系的表之间是否有业务关系:
order与item:通过订单明细表作为桥梁,order与item之间是多对多关系。一个订单对应多个订单明细,一个订单明细对应一个商品信息,所以一个订单对应多个商品信息。一个商品信息对应多个订单明细,一个订单明细对应一个订单,所以一个商品信息对应多个订单。
user与item:通过订单和订单明细作为桥梁,user与item之间是多对多关系。一个用户对应多个订单,一个订单对应多个订单明细,一个订单明细对应一个商品信息,所以一个用户对应多个商品信息。一个商品信息对应多个订单明细,一个订单明细对应一个订单,一个订单对应一个用户,所以一个商品信息对应多个用户。
高级结果集映射(一对一、一对多、多对多)
一对一查询:查询订单信息,关联查询创建订单的用户信息。
SQL语句:根据查询需求确定查询的主表为订单表,关联表为用户表。
SELECT o.*,
u.username,
user.gender,
user.address
FROM ORDER AS o, USER AS u
WHERE o.user_id = u.id
resultType:
关联查询POJO定义:
将SQL语句查询的结果映射到POJO中,POJO中必须包含所有的查询列。原始Order的POJO不能满足全部字段,需要创建一个扩展自Order的综合POJO类型。
public class Order {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//get、set省略
}
public class OrderCustom extends {
private String username;
private Integer gender;
private String address;
//get、set省略
}
映射文件OrderMapper.xml
<mapper namespace="xxx.xxx.OrderMApper">
<select id="findOrderWithUser" resultType="xxx.xxx.OrderCustom">
SELECT o.*,
u.username,
user.gender,
user.address
FROM ORDER AS o, USER AS u
WHERE o.user_id = u.id
</select>
</mapper>
接口OrderMapper.java
public interface OrderMapper {
List<OrderCustom> findOrderWithUser();
}
resultMap:
关联查询POJO的定义
public class Order {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
private User user;
//get、set省略
}
映射文件OrderMApper.xml
<mapper namespace="xxx.xxx.OrderMApper">
<resultMap type="xxx.xxx.Order" id="OrderWithUserMap">
<!-- 配置映射的订单信息 -->
<!-- id:指定查询列中的唯一标识,订单信息中的唯一标识
如果是多列组成的唯一标识,则定义多个id。
-->
<id column="id" property="id" />
<result column="user_id" property="userId" />
......
<!-- 配置映射的关联用户信息
association:用于映射关联查询单个对象的信息
property:要将关联查询的单个对象信息映射到POJO对象中的哪个POJO属性
-->
<association property="user" type="xxx.xxx.User">
<!-- id:关联查询用户的唯一标识
column:指定唯一标识的列
property:映射到User的哪个属性
-->
<id column="user_id" property="id" />
<result column="username" property="username"/>
......
</association>
</resultMap>
<select id="findOrderWithUser" resultMap="OrderWithUserMap">
SELECT o.*,
u.username,
user.gender,
user.address
FROM ORDER AS o, USER AS u
WHERE o.user_id = u.id
</select>
</mapper>
接口OrderMapper.java
public interface OrderMapper {
List<Order> findOrderWithUser();
}
实现一对一查询小结:
resultType:相对简单,如果POJO中没有包含查询出来的所有列,需要增加列名对应的属性才能完成映射。如果对查询结果没有特殊要求,建议使用resultType。
resultMap:需要单独定义resultMap,实现比较麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射POJO的属性中。resultMap可以实现延迟加载,而resultType无法实现延迟加载。
一对多查询:查询订单及订单明细的信息
SQL语句:根据查询需求确定查询的主表为订单表,关联查询表为订单明细表。
SELECT o.*,
u.username,
u.gender,
u.address,
ud.id orderdetail_id,
ud.item_id,
ud.item_num,
ud.order_id
FROM ORDER AS o, USER AS u, ORDERDETAIL AS od
WHRER o.user_id = u.id AND o.id = od.order_id
关联查询POJO定义
public class Order {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
private User user;
private List<OrderDetail> orderDetails;
//get、set省略
}
public class OrderDetail {
private Integer id;
private Integer itemId;
private Integer itemNum;
private Integer orderId;
//get、set方法省略
}
映射文件OrderMapper.xml
<mapper>
<!-- 订单关联的用户及订单明细resultMap -->
<resultMap type="xxx.xxx.Order" id="OrderWithUserAndOrderDetail">
<!-- 订单信息 -->
<id 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="user" javaType="xxx.xxx.User">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="gender" property="gender"/>
<result column="address" property="address"/>
</association>
<!-- 订单明细信息 -->
<!--
一个订单关联查询出了多条明细,要使用collection进行映射
collection:将关联查询到的多条记录映射到集合对象中,记录的主键相同视为同一个订单的多个订单明细
property:需要映射到Order中的orderDetails的属性上
ofType:指定映射到List集合属性中的POJO类型
-->
<collection property="orderDetails" ofType="xxx.xxx.OrderDetail">
<!-- id:订单明细的唯一标识 -->
<id column="orderdetail_id" property="id" />
<result column="item_id" property="itemId" />
<result column="item_num" property="itemNum" />
<result column="order_id" property="orderId" />
<collection>
</resultMap>
<!-- 查询订单及关联的用户和订单明细信息 -->
<select id="findOrderWithUserAndOrderDetail" resultMap="OrderWithUserAndOrderDetail">
SELECT o.*,
u.username,
u.gender,
u.address,
ud.id orderdetail_id,
ud.item_id,
ud.item_num,
ud.order_id
FROM ORDER AS o, USER AS u, ORDERDETAIL AS od
WHRER o.user_id = u.id AND o.id = od.order_id
</select>
</mapper>
接口OrderMapper.java
public interface OrderMapper {
List<Order> findOrderWithUserAndOrderDetail();
}
多对多查询:查询用户及用户购买的商品信息
SQL语句:根据查询需求确定查询的主表为用户表,由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表为:订单表、订单明细表、商品表。
SELECT o.*,
u.username,
u.gender,
u.address,
ud.id orderdetail_id,
ud.item_id,
ud.item_num,
ud.order_id
i.name item_name,
i.detail item_detail,
i.price item_price,
i.prc item_pic
FROM ORDER AS o, USER AS u, ORDERDETAIL AS od, ITEM AD i
WHRER o.user_id = u.id AND o.id = od.order_id AND od.item_id = i.id
关联查询POJO定义
public class User {
private Integer id;
private String username;
private Integer gender;
private String address;
private List<Order> orders;
//set、get方法省略
}
public class Order {
private Integer id;
private Integer userId;
private String number;
private List<OrderDetail> orderDetails;
//set、get方法省略
}
public class OrderDetail {
private Integer id;
private Integer orderId;
private Integer itemId;
private Integer itemNum;
private Item item;
//set、get方法省略
}
public class Item {
private Integer id;
private String name;
private String detail;
private Double price;
private String picture;
//set、get方法省略
}
映射文件UserMapper.xml
<mapper namespace="xxx.xxx.UserMapper">
<!-- 查询用户及购买的商品 -->
<resultMap type="xxx.xxx.User" id="UserWithItem">
<id column="user_id" property="id" />
<result column="username" property="username" />
<result column="gender" property="gender" />
<result column="address" property="address" />
<collection property="orders" ofType="xxx.xxx.Order">
<id column="id" property="id" />
<result column="number" property="number" />
<result column="createtime" property="create time" />
<result column="note" property="note" />
<collection property="orderDetails" ofType="xxx.xxx.OrderDetail">
<id column="orderdetail_id" property="id" />
<result column="order_id" property="orderId" />
<result column="item_id" property="itemId" />
<result column="item_num" property="itemNum" />
<association property="item" javaType="xxx.xxx.Item">
<id column="item_id" property="id" />
<result column="item_name" property="name" />
<result column="item_detail" property="detail" />
<result column="item_price" property="price" />
<result column="item_pic" property="picture" />
</association>
</collection>
</collection>
</resultMap>
<select id="findUserWithItems" resultMap="UserWithItem">
SELECT o.*,
u.username,
u.gender,
u.address,
ud.id orderdetail_id,
ud.item_id,
ud.item_num,
ud.order_id
i.name item_name,
i.detail item_detail,
i.price item_price,
i.prc item_pic
FROM ORDER AS o, USER AS u, ORDERDETAIL AS od, ITEM AD i
WHRER o.user_id = u.id AND o.id = od.order_id AND od.item_id = i.id
</select>
</mapper>
接口UserMapper.java
public interface UserMapper {
List<User> findUserWithItems();
}
resultMap总结
resultType:
作用:将查询结果按照SQL语句返回结果的列名与POJO属性名一致的映射到POJO中。
场合:常见一些明细记录的展示,比如:用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每条记录映射到POJO中,在前端页面遍历list即可。
resultMap:
使用association和collection分别完成一对一和一对多的高级映射。
association:
作用:将关联查询信息映射到一个POJO中。
场合:为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的POJO属性中,比如:查询订单及关联的用户信息。使用resultType无法将查询结果映射到POJO对象的POJO属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。
collection:
作用:将关联查询信息映射到一个List集合中。
场合:为了方便查询遍历关联信息,可以使用collection将关联信息映射到List集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块List中,将菜单列表映射到模块对象的菜单List属性中,方便对查询结果集进行遍历查询。如果使用resultType无法将查询结果集映射到List集合中。
Mybatis延迟加载
什么是延迟加载?
resultMap可以实现高级映射(使用association和collection分别实现一对一及一对多映射),association、collection具备延迟加载功能。
需求:如果查询订单且关联查询用户信息,如果先查订单信息即可满足要求,当需要查询用户信息时再查询用户信息。把对用户信息的按需查询称为延迟加载。
延迟加载:先从单表中查询,需要时再从关联表进行关联查询,大幅提高数据库性能,因为查询单表要比查询多张表数度快。
使用association实现延迟加载
Mybatis查询缓存(一级缓存、二级缓存)
Mybatis和Spring进行整合(掌握)