视频
各层之间的调用顺序是固定的,不允许跨层访问:
界面层<--------------->业务逻辑层<----------------->数据访问层
是一个半成品的软件,将所有的公共的,重复的功能解决掉,帮助程序快速高效的进行开发,它是可复用的,可扩展的。
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.6version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.32version>
dependency>
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
resources>
build>
文件名:jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=root
全局配置
DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties">properties>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
<typeAliases>
<package name="org.example.pojo"/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
environment>
environments>
<mappers>
<package name="org.example.mapper">package>
mappers>
configuration>
以User实体类为例
根据规范第1条:创建一个文件夹
根据规范第2条:新建同名的接口和xml文件
public interface UsersMapper {
// 获取全部数据
List<Users> getAll();
// 通过id获取数据
Users getUser(Integer id);
//更新数据
int updataUser(Users users);
// 按名字模糊查询
List<Users> getUserByName(String userName);
// 插入用户
int insertUser(Users users);
// 根据id删除用户
int deleteUser(Integer id);
}
属性parameterType
:传入的参数。如果传入的参数为实体类,值为实体类的驼峰命名法。如果为其他则可省略此属性。
属性resultType
:返回值。返回的值为实体类的类型或实体类的List集合,值为实体类的驼峰命名法。如果为其他则可省略此属性。
注意:
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mapper.UsersMapper">
<select id="getUser" resultType="Users" parameterType="Integer">
select id,username,birthday,sex,address from users where id=#{id}
select>
<select id="getAll" resultType="Users">
select id,username,birthday,sex,address from users
select>
<update id="updataUser" parameterType="users">
update users set username=#{userName},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
update>
<select id="getUserByName" parameterType="String" resultType="Users">
select id,username,birthday,sex,address from users where username like '%${name}%'
select>
<insert id="insertUser" parameterType="users">
insert into users (username,birthday,sex,address) values (#{userName},#{birthday},#{sex},#{address})
insert>
<delete id="deleteUser" parameterType="Integer">
delete from users where id=#{id}
delete>
mapper>
通过动态代理执行sql语句:
mapper = sqlSession.getMapper(UsersMapper.class);
List all = mapper.getAll();
注意: 更新、删除、插入操作需要手动提交事务
public class MyTest {
SqlSession sqlSession;
UsersMapper mapper;
// 日期格式化工具
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
// @Before作用:在所有方法执行前调用执行
@Before
public void openSession() throws IOException {
// 1. 读取核心配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 创建工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3. 取出sqlsession对象
sqlSession = factory.openSession();
// 4. 获取UsersMapper.xml的代理对象
mapper = sqlSession.getMapper(UsersMapper.class);
}
// @After作用:有所有方法执行完毕后调用执行
@After
public void closeSession(){
sqlSession.close();
}
// 获取所有数据
@Test
public void testGetAll(){
// 5. 代理对象执行目标对象实现的接口中的方法
List<Users> all = mapper.getAll();
System.out.println(all);
}
// 获取id的数据
@Test
public void testGetUser(){
Users user= mapper.getUser(3);
System.out.println(user);
}
// 更新数据
@Test
public void updataUser() throws ParseException {
Users users = new Users(3,"张大明", sf.parse("2000-1-1"),"1","北京朝阳");
int n = mapper.updataUser(users);
sqlSession.commit();//手动提交事务
}
// 模糊查询
@Test
public void testgetUserByName(){
List<Users> users = mapper.getUserByName("三");
}
// 插入数据
@Test
public void testInsertUser() throws ParseException {
int n = mapper.insertUser(new Users("呵呵", sf.parse("2002-2-2"), "2", "上海陆家嘴"));
sqlSession.commit();
}
//根据id删除用户
@Test
public void testDeleteUser(){
int n = mapper.deleteUser(31);
sqlSession.commit();
}
}
parameterType
传入的参数是普通类型则#{}里面随便写parameterType
传入的参数是引用类型,则#{}里面就要写实体类中成员变量的名称,而且区分大小写。concat
方法和#{}拼接字符串,示例:'%${name}%'
改写为:concat ('%',#{name},'%')
${}
最重要的作用-----字符串替换:// 接口文件
// 按名字模糊查询,高级版
List<Users> getUserByNameBest(
// 使用注解@Param给参数设置别名
@Param("columnName")
String columnName,
@Param("columnValue")
String columnValue);
测试代码中传入两个参数,第一个参数使用${}
会替换为email字符串,第二个参数使用占位符#{}
,防止sql注入。
<select id="getUserByNameBest" parameterType="String" resultType="Users">
select id,name,email,age from users where ${columnName} like concat ('%',#{columnValue},'%')
select>
测试代码:
// 模糊查询 高级版 指定列模糊查询
@Test
public void testgetUserByNameBest(){
List<Users> users = mapper.getUserByNameBest("email","126");
}
使用1.9.2中的代码
# 第一种情况
select * from users where ${columnName} like concat ('%',#{columnValue},'%')
#执行后
select * from users where email like concat ('%',?,'%')
# 可以看到#{}是占位符,${columnName}替换为字符串:email
# 第二种情况
select * from users where #{columnName} like concat ('%',${columnValue},'%')
# 执行后
select * from users where ? like concat ('%',126,'%')
# 从上面两个例子可以很明确的看出#{}与${}的作用
功能:在执行完相关代码后自动返回主键的id值到入参的实体类中。
<selectKey keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
selectKey>
selectKey
标签的参数详解:
keyProperty
:主键在数据库表中的列名resultType
:主键的类型,可能是UUIDorder
:
标签体内的代码的执行时机示例::
接口的mapper.xml文件
<insert id="insertUserReturnId" parameterType="users">
<selectKey keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
selectKey>
insert into users (name,email,age) values (#{name},#{email},#{age})
insert>
测试代码
// 插入数据,并返回主键id
@Test
public void testInsertUserReturnId() throws ParseException {
Users user = new Users("张小明", "[email protected]", 3);
int n = mapper.insertUserReturnId(user);
sqlSession.commit();
System.out.println(user);
}
输出:Users{id=11, name='张小明', email='[email protected]', age=3}
结论: 新增数据,主键值自动返回给传入的实体类参数。
java中生成UUID
// UUID 全球唯一字符串
@Test
public void testUUID(){
UUID uuid = UUID.randomUUID();
System.out.println("uuid = " + uuid);
}
sql中生成UUID
select uuid()
自定义代码片段自定义代码片段
:,可以将复杂、重复的代码自定义为片断。属性id的值自定义
使用自定义的代码片段
:用来引用定义的代码片断。属性refid的值为片断中属性id的值
示例:
<sql id="allColumns">
id,name,email,age
sql>
<select id="getUser" resultType="Users" parameterType="Integer">
select <include refid="allColumns">include> from users where id=#{id}
select>
:实现多条件查询功能:
标签代替sql语句中的where,在标签内通过使用
标签等进行条件判断,达到多条件查询的目的
注意: 入参为实体类
示例:同下面 2.5
:条件判断
:条件判断功能:条件为真,则使用标签内的代码
注意:
标签属性test进行条件判断,注意判断是否是空字符串时使用单引号
- 在
标签内
的语句记得加
and
示例:
<!-- <where> 实现全条件模糊查询-->
<select id="getAllWhere" parameterType="users" resultType="users">
select <include refid="allColumns"></include> from users
<where>
<if test="userName != null and userName != ''">
and username like concat('%',#{userName},'%')
</if>
<if test="birthday != null">
and birthday = #{birthday}
</if>
<if test="sex != null and sex != ''">
and sex = #{sex}
</if>
<if test="address != null and address != ''">
and address like concat('%',#{address},'%')
</if>
</where>
</select>
:更新数据原来的更新出现的问题:
解决上述问题,则需要判断传入的对象中每个成员变量是否有值来确定要更新的列
标签的作用: 代替更新语句中的set关键字,可在标签内使用
判断成员变量是否有值,来决定是否更新某列。
注意:
- 更新操作最少要有一条数据被更新,否则会报错
标签包裹的代码,记得加
,
号。
示例:
<!-- 动态sql,更新数据 <set>标签-->
<update id="updataUserSet" parameterType="users">
update users
<set>
<if test="userName != null and userName != ''">
username=#{userName},
</if>
<if test="birthday != null">
birthday=#{birthday},
</if>
<if test="sex != null and sex != ''">
sex=#{sex},
</if>
<if test="address != null and address != ''">
address=#{address},
</if>
</set>
where id=#{id}
</update>
循环遍历:条件查询、批量删除、批量更新
标签内的属性:
标签开始前的括号
标签结束后的括号查询表中名字是王五和张三的数据,原生sql语句如下
select * from users
where username in ('王五','张三')
使用foreach标签
<select id="getByIds" resultType="Users">
select <include refid="allColumns">include> from users
where username in
<foreach collection="array" item="item" separator="," open="(" close=")">
#{item}
foreach>
select>
通过对比可以看出
('王五','张三')
// 等同于下面的代码
<foreach collection="array" item="item" separator="," open="(" close=")">
#{item}
</foreach>
foreach遍历的是实体类users的list集合,所以item的值就是一个users元素,要取实体类中成员变量的值,需要使用user.username。
mapper.xml配置文件:
<insert id="updataForeach">
insert into users (username,birthday,sex,address) values
<foreach collection="list" item="user" separator=",">
(#{user.userName},#{user.birthday},#{user.sex},#{user.address})
foreach>
insert>
注意:
要使用批量更新需要在jdbc的配置文件jdbc.properties的url后面新增代码 &allowMultiQueries=true
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
jdbc.username=root
jdbc.password=root
;
。 <update id="updataForeach">
<foreach collection="list" separator=";" item="item">
update users
<set>
<if test="item.userName != null and item.userName != ''">
username = #{item.userName},
if>
<if test="item.birthday != null">
birthday = #{item.birthday},
if>
<if test="item.sex != null and item.sex != ''">
sex = #{item.sex},
if>
<if test="item.address != null and item.address != ''">
address = #{item.address},
if>
set>
where id = #{item.id}
foreach>
update>
在下面示例中,参数(Date begin,Date end),对应到mapper文件中使用时,按照参数的顺序,依次取值,
#{arg0}
取得是第一个参数begin
的值,#{arg1}
取得是第二个参数的值end
。无论有多少参数只需要改变#{arg顺序}
中的顺序就可以
缺点: 可读性差
mapper.xml文件中代码
<select id="getBetweenDate" resultType="users">
select <include refid="allColumns">include>
from users
where birthday between #{arg0} and #{arg1}
select>
见示例:1.9.2
使用方法
- 将要传入的参数,存入map的键值对
- 在xml文件中,直接使用map键值对的key就可获取到value的值
示例中传入的map键值对是:{begin=sf.parse("2000-01-01"),end=sf.parse("2002-12-31")}
在使用中,直接通过map的key#{begin} 、 #{end}
就可以获取到对应的value值
测试方法
@Test
public void testMapGetBEtween() throws ParseException {
Map<String,Date> map = new HashMap<>();
map.put("begin", sf.parse("2000-01-01"));
map.put("end", sf.parse("2002-12-31"));
mapper.getBetweenDateByMap(map);
}
<select id="getBetweenDateByMap" resultType="users">
select <include refid="allColumns">include>
from users
where birthday between #{begin} and #{end}
select>
注意:xml中返回值类型为map
<select id="getSimpleMap" resultType="map">
select name,email
from users
where id = #{id}
select>
输出map的值:{name=王五, [email protected]}
<select id="getSimpleMap" resultType="map">
select name as n ,email as e
from users
where id = #{id}
select>
输出map的值:{[email protected], n=王五}
<!-- 返回多行数据,返回map的list集合-->
<!-- List<Map> getRowsMap();-->
<select id="getRowsMap" resultType="map">
select name ,email from users
</select>
问题: 列名与实体类的成员变量名不一致,并且resultType属性的值为实体类名,执行时,代码不报错,但是返回的结果为空。
数据库中列名
实体类中成员变量名
mapper.xml中给列设置别名
- 在
标签内使用属性
resultMap
,属性值自定义- 创建映射关系,
标签内
id
属性值为上面自定义的值,type
属性值为实体类- 主键映射:
- 非主键映射:
<select id="getAllMap" resultMap="mapToColumn">
select studentid ,studentname ,studentemail ,studentage
from student
select>
<resultMap id="mapToColumn" type="student">
<id property="id" column="studentid">id>
<result property="name" column="studentname">result>
<result property="email" column="studentemail">result>
<result property="age" column="studentage">result>
resultMap>
需要注意的地方很多
- 一对多关联查询时,select查询语句中要使用
left join
,以左表为主表。客户3没有订单,如果使用inner join
关联,查询结果为null,就算客户没有订单,也应该查询到该客户的信息。- 当两个表有相同的列名时,必须取别名
- 在xml文件中做实体类的成员变量和表中列名的映射时,列名相同的必须使用别名,不然查询的结果不正确,相同列名的结果会搞混。
- 使用的标签和属性就一一对应的看。
// 客户表
public class Customer {
private Integer id;
private String name;
private Integer age;
// 对应的订单,一对多,一个客户可以有多个订单
private List<Orders> ordersList;
}
// 订单表
public class Orders {
private Integer id;
private String orderNumber;
private double orderPrice;
// 对应的客户,多对一,每个订单只对应一个客户
private Customer cus;
<select id="getOneToMore" resultMap="oneToMore">
select customer.id as cid,name,age,orders.id as oid,orderNumber,orderPrice,customer_id
from customer left join orders on customer.id = orders.customer_id
where customer.id=#{id};
select>
<resultMap id="oneToMore" type="customer">
<id property="id" column="cid">id>
<result property="name" column="name">result>
<result property="age" column="age">result>
<collection property="ordersList" ofType="orders">
<id property="id" column="oid">id>
<result property="orderNumber" column="orderNumber">result>
<result property="orderPrice" column="orderPrice">result>
collection>
resultMap>
输出:
Customer{id=1, name='张三', age=22, ordersList=[Orders{id=11, orderNumber='20', orderPrice=22.22, customer=null}, Orders{id=12, orderNumber='60', orderPrice=16.66, customer=null}]}
- 与一对多最大的区别是关系映射使用的标签,引用类型为实体类使用的标签 与 引用类型为实体类的list集合所使用的标签不同,标签内的属性也不同。
2.在select语句中,多对一的联表使用inner join
,每个订单肯定对应一个客户,不可能出现客户为null的情况。- 其他需要注意的地方与一对多联表查询一样
<select id="getMoreToOne" resultMap="moreToOne">
select orders.id oid,orderNumber,orderPrice,customer.id cid,name,age
from orders inner join customer on customer_id=customer.id
where orders.id=#{id}
select>
<resultMap id="moreToOne" type="orders">
<id property="id" column="oid">id>
<result property="orderNumber" column="orderNumber">result>
<result property="orderPrice" column="orderPrice">result>
<association property="cus" javaType="customer">
<id property="id" column="cid">id>
<result property="name" column="name">result>
<result property="age" column="age">result>
association>
resultMap>
输出:
moreToOne = Orders{id=11, orderNumber='20', orderPrice=22.22, customer=Customer{id=1, name='张三', age=22, ordersList=null}}
<transactionManager type="JDBC">transactionManager>
false
// 3. 取出sqlsession对象
sqlSession = factory.openSession(参数);//参数默认为`false`
sqlSession.commit();
来提交事务。sqlSession.commit();
来提交事务。commit()
方法执行,就会清空所有缓存。重点:所有 // 获取id的数据
@Test
public void testGetUser(){
Users user= mapper.getUser(3);
System.out.println("第一次查询 = " + user);
Users user1= mapper.getUser(3);
System.out.println("第二次查询 = " + user1);
}
查询结果:
==> Preparing: select id ,name,email,age from users where id=?
==> Parameters: 3(Integer)
<== Columns: id, name, email, age
<== Row: 3, 王五, wangwu@163.com, 22
<== Total: 1
第一次查询 = Users{id=3, name='王五', email='wangwu@163.com', age=22}
第二次查询 = Users{id=3, name='王五', email='wangwu@163.com', age=22}
从查询结果中可以看到,Preparing语句只被执行了一次。
// 获取id的数据
@Test
public void testGetUser(){
Users user= mapper.getUser(3);
System.out.println("第一次查询 = " + user);
sqlSession.commit();
Users user1= mapper.getUser(3);
System.out.println("第二次查询 = " + user1);
}
查询的结果:
==> Preparing: select id ,name,email,age from users where id=?
==> Parameters: 3(Integer)
<== Columns: id, name, email, age
<== Row: 3, 王五, wangwu@163.com, 22
<== Total: 1
第一次查询 = Users{id=3, name='王五', email='wangwu@163.com', age=22}
==> Preparing: select id ,name,email,age from users where id=?
==> Parameters: 3(Integer)
<== Columns: id, name, email, age
<== Row: 3, 王五, wangwu@163.com, 22
<== Total: 1
第二次查询 = Users{id=3, name='王五', email='wangwu@163.com', age=22}
可以看到提交了两次select语句Preparing: select id ,name,email,age from users where id=?
目标对象必须实现接口,代理对象只能增强目标对象实现的接口的方法。
动态代理的实现是固定的:
public interface TargetInterface1 {
void tI1fun1();
String tI1fun2(int arg);
}
public interface TargetInterface2 {
void tI2Fun1();
String tI2Fun2(int num);
}
public class TargetClassOne implements TargetInterface1, TargetInterface2 {
@Override
public void tI1fun1() {System.out.println("实现接口1的方法1");}
@Override
public String tI1fun2(int num) {System.out.println("实现接口1的方法2,接收参数"+num);
return "接口1方法2返回的内容";}
@Override
public void tI2Fun1() {System.out.println("实现接口2的方法1"); }
@Override
public String tI2Fun2(int num) {System.out.println("实现接口2的方法2,接收参数"+num);
return "接口2方法2返回的内容"; }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactroy<T> {
public T targetObject;
public ProxyFactroy(T targetObject) {
this.targetObject = targetObject;
}
public Object getProxy(){
return Proxy.newProxyInstance(
// ClassLoader loader, 类加载器,完成目标对象的加载
targetObject.getClass().getClassLoader(),
// Class>[] interfaces, 目标对象实现的所有接口
targetObject.getClass().getInterfaces(),
// InvocationHandler h 实现代理功能的接口,使用匿名内部类实现
new InvocationHandler() {
@Override
public Object invoke(
Object proxy, // 创建的代理对象,动态代理的目的就是要它
Method method, // 目标对象实现的接口中的方法
Object[] args // 目标方法的参数
) throws Throwable {
System.out.println("创建的代理对象中增强的功能1");
Object invoke = method.invoke(targetObject, args);
System.out.println("创建的代理对象中增强的功能2");
return invoke;
}
}
);
}
}
public void testjdk(){
// 将目标类出入代理对象工厂
ProxyFactroy proxy = new ProxyFactroy(new TargetClassOne());
// 获得动态代理对象(强转为接口1)
TargetInterface1 proxyObj = (TargetInterface1)proxy.getProxy();
// 可以执行目标类实现接口的方法
proxyObj.tI1fun1();
String s = proxyObj.tI1fun2(20);
System.out.println(s);
System.out.println("====================上面是接口1的方法,下面是接口2的方法===========================");
// 获得动态代理对象(强转为接口2)
TargetInterface2 proxyObj1 = (TargetInterface2)proxyObj;
// 可以执行目标类实现接口的方法
proxyObj1.tI2Fun1();
String s1 = proxyObj1.tI2Fun2(50);
System.out.println(s1);
}
创建的代理对象中增强的功能1
实现接口1的方法1
创建的代理对象中增强的功能2
创建的代理对象中增强的功能1
实现接口1的方法2,接收参数20
创建的代理对象中增强的功能2
接口1方法2返回的内容
====================上面是接口1的方法,下面是接口2的方法===========================
创建的代理对象中增强的功能1
实现接口2的方法1
创建的代理对象中增强的功能2
创建的代理对象中增强的功能1
实现接口2的方法2,接收参数50
创建的代理对象中增强的功能2
接口2方法2返回的内容