一、框架
为了简化开发步骤,省去开发细节,提高编写代码的效率,可以使用框架来完成一些底层的操作。例如:
- 数据类型转换 => 交给框架
- 数据校验 => 交给框架
- 将增删改查集中管理 => 交给框架
- 异常的统一处理
- 简化jdbc => 交给框架
- 使用框架组合各种技术
(一) 框架分类
-
持久性框架
与数据库打交道
ORM框架: object(java)对象 relationship (关系型数据库 mysql) mapping (映射-java对象和数据库之间)
常用的有:mybatis(大型项目-直接管理sql),hibernate(小型项目-不需要直接写sql语句),jpa,jooq … -
mvc 框架
跟 web 应用程序打交道
Model(模型-数据) View(视图-数据展现方式) Controller(控制器)
把模型数据准备好, 把数据放入作用域, 最后通过jsp视图展现数据
常用的有:springmvc, struts2 -
spring 框架 (容器技术)
把各种框架进行集成,让他们协同工作
二、MyBatis
MyBatis 是支持自定义SQL、存储过程以及高级映射的优秀的持久层框架。避免了JDBC 代码和手动设置参数以及获取结果集。可以使用简单的XML或注解来进行配置。
以前 iBatis ->被收购后 apache MyBatis
(一) MyBatis使用步骤
- 添加依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
-
添加配置文件 mybatis-config.xml
用这个配置文件来告诉 mybatis 如何连接数据库
告诉 mybatis 到哪里去找映射关系 -
提供一个 映射文件
用来管理 sql 语句,描述 sql 语句与数据库表之间的映射关系 -
.调用 mybatis api 使用映射文件真正执行增删改查
InputStream in = Resources.getResourceAsStream("配置文件的路径");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = factory.openSession();
sqlSession
.insert("命名空间+sql的名字", sql需要的参数对象)
.update("命名空间+sql的名字", sql需要的参数对象)
.delete("命名空间+sql的名字", sql需要的参数对象)
.selectOne("命名空间+sql的名字", sql需要的参数对象) 结果只有一个时
.selectList("命名空间+sql的名字", sql需要的参数对象) 结果是list集合
对增删改
sqlSession.commit();
关闭
sqlSession.close();
(二) Mybatis 映射文件
- 新增
<!-- #{sname} 用来获取 Student 参数对象中的 sname属性-->
<!-- useGeneratedKeys="true" 是告诉 mybatis 要使用由数据库产生的主键值 -->
<!-- keyProperty="主键对应的属性名" -->
<insert id="abc" parameterType="domain.Student"
useGeneratedKeys="true" keyProperty="sid">
insert into student(sid, sname, birthday, sex)
values ( null, #{name}, #{birthday}, #{sex})
</insert>
- 删除
<delete id="delete" parameterType="int">
delete from student where sid = #{sid}
</delete>
- 查询所有
<select id="findAll" resultType="domain.Student">
select sid,sname name,birthday,sex from student
</select>
- 根据id查询
<select id="findById" resultType="domain.Student" parameterType="int">
select sid,sname name,birthday,sex from student where sid = #{sid}
</select>
- 更新
<update id="update" parameterType="domain.Student">
update student set sname=#{name}, birthday=#{birthday}, sex=#{sex} where sid=#{sid}
</update>
动态更新:
<!-- 动态更新列
Student stu = new Student();
stu.setSid(1001);
stu.setSex("男");
update student set sex=#{sex} where sid=#{sid}
用 set 标签可以去除多余的逗号
-->
<update id="update" parameterType="domain.Student">
update student
<set>
<if test="name != null">
sname=#{name},
</if>
<if test="birthday != null">
birthday=#{birthday},
</if>
<if test="sex != null">
sex=#{sex},
</if>
</set>
where sid=#{sid}
</update>
- 分页查询
<!-- m , n
java.util.Map -> map
java.util.List -> list
java.lang.String -> string
map.put("m", 0);
map.put("n", 5);
-->
<select id="findByPage" parameterType="map" resultType="domain.Student">
select sid,sname name,birthday,sex from student limit #{m}, #{n}
</select>
(三) 通过日志工具监控mybatis生成的sql语句
- 添加logback依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
- 添加一个 logback.xml 到 resources文件夹
<!-- 用来控制查看那个类的日志内容(对mybatis name 代表命名空间) -->
<logger name="mapper.StudentMapper" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</logger>
三、动态sql和Mapper接口
(一) 动态 sql 生成
- foreach 循环
delete from 表 where id in(1008,1011,1012);
<!-- list (1008,1011,1012, 1013) -->
<delete id="" parameterType="list">
delete from 表 where id in
<foreach collection="list" item="x" open="(" close=")" separator=",">#{x}</foreach>
</delete>
- if where 生成动态sql
<where> 能够生成 where 关键字,并去除多余的 and
<if test="条件"> sql 片段 </if>
-
对于大于小于条件的处理
方法1: 对每个特殊字符转转义,例如 < <
方法2: <![CDATA[ 内容 ]]> -
#{ } 与 ${ } 的区别
- #{ } 底层是替换为 ?, 只能占位值
- ${ } 底层是直接拼接字符串, 有注入攻击问题
- 尽量使用 #{ } , 只有在某些功能不能用 #{ } 实现时(例如排序)采用 ${ }
(二) Mapper 接口
- 基础接口
- @Insert
- @Option(useGeneratedKeys =true.keyProperty=“sid”)
- @Update
- @Delete
- @Select
@Insert("insert into student(sid,sname,birthday,sex) values(null,#{sname}, #{birthday}, #{sex})")
@Options(useGeneratedKeys = true, keyProperty = "sid")
void insert(Student student);
@Update("update student set sname=#{sname}, birthday=#{birthday}, sex=#{sex} where sid=#{sid}")
void update(Student student);
@Delete("delete from student where sid=#{sid}")
void delete(int sid);
@Select("select * from student")
List<Student> findAll();
@Select("select * from student where sid = #{sid}")
Student findById(int sid);
@Select("select * from student limit #{m}, #{n}")
List<Student> findByPage(Map map);
- 原理
// 这个类是使用了 jdk的动态代理技术 在代码运行期间生成的类
public class $Proxy10 implements StudentMapper{
private SqlSession sqlSession;
public $Proxy10(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
// 其中 sql语句从@Insert注解获得, 参数对象就是student
public void insert(Student student) {
sqlSession.insert(sql, 参数对象)
}
// 其中 sql语句从@Select注解获得
public List<Student> findAll() {
return sqlSession.selectList(sql);
}
}
- 基本使用
(1)在mybatis的配置文件中指定映射接口的包名,类名
<mappers>
<!-- 指定映射接口的 包名.类名 -->
<mapper class="mapper.StudentMapper"></mapper>
</mappers>
(2)在mapper接口中写好sql语句
public interface StudentMapper {
@Insert("insert into student(sid,sname,birthday,sex) values(null,#{sname}, #{birthday}, #{sex})")
@Options(useGeneratedKeys = true, keyProperty = "sid")
void insert(Student student);
}
(3)使用mapper接口执行sql语句
public class TestStudentMapper {
static SqlSessionFactory factory;
static {
// 读取配置文件
try(InputStream in = Resources.getResourceAsStream("mybatis-config.xml")){
// 创建 sqlSession 工厂类
factory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testInsert() throws ParseException {
SqlSession sqlSession = factory.openSession();
// 先通过 sqlSession 获得接口对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student stu = new Student();
stu.setSname("啊啊啊");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
stu.setBirthday(sdf.parse("1999-5-5"));
stu.setSex("男");
// 通过接口对象中声明的方法 执行sql语句
mapper.insert(stu);
sqlSession.commit();
sqlSession.close();
}
}
(三) Mapper接口的不足
- 接口方法不能直接应用多个方法参数
解决方法:
(1) 用map传递多个参数, 每个参数对应map中的一个键值对
(2) 用@Param注解
@Select("select * from student limit #{m}, #{n}")
List<Student> findByPage(Map map);
// 同时根据姓名和性别查询
@Select("select * from student where sname=#{a} and sex=#{b}")
List<Student> find1(@Param("a") String sname, @Param("b") String sex);
- Mapper 接口中不能有方法的重载
解决方法:定义方法时不要方法名冲突
异常信息:
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for 接口名.方法名(重复的)
- 使用Mapper接口方式实现动态sql比较复杂
解决方法: 结合Mapper接口和xml文件,接口和xml中必须一一对应
(四) 复杂sql映射
除xml mapper 以外,还可以利用 @DeleteProvider @InsertProvider @SelectProvider @UpdateProvider
生成复杂sql
public class StudentSqlProvider {
// 1, 2, 3, list参数名 固定为 list
public static String getDeleteByIdsSql(@Param("list") List<Integer> aaa) {
StringBuilder sb = new StringBuilder(128);
sb.append("delete from student where sid in");
sb.append("(");
for (int i = 0; i < aaa.size(); i++) {
sb.append(aaa.get(i));
if(i < aaa.size()-1) {
sb.append(",");
}
}
sb.append(")");
return sb.toString();
}
}
public interface StudentMapper {
// 由 @DeleteProvider 注解找到一条sql语句,供 deleteByIds 方法使用
@DeleteProvider(type = StudentSqlProvider.class,method = "getDeleteByIdsSql")
void deleteByIds(List<Integer> ids);
}
(五) 高级映射
当表列 与 属性名 不一致 时==> 给列起别名
四、缓存
select * from student where id = 1001 student
select * from student where id = 1001
- sqlsession 自带缓存功能,缓存没有才查库,缓存中有,直接返回缓存的结果,称为一级缓存
- 在sqlsession创建时建立,sqlsession.close 时清空
- 每个sqlsession有自己独立的缓存
二级缓存:
需要额外设置,但可以允许多个 sqlsession 共享缓存数据,二级缓存中的对象需要实现序列化接口
好处: 可以较大提升查询效率, 但是增删改频繁的情况下,不适合开启二级缓存
xml mapper 二级缓存的配置: 在 xml 映射文件中添加<cache/>
标签即可
接口 mapper 二级缓存的配置: 在接口上添加 @CacheNamespace 注解