基础知识学习
框架:开发中说的框架,通常是一种解决方案,其内部封装了一些细节,使开发者可以通过很少的时间实现功能,提高开发效率。
三层架构:
表现层:用于展示数据。
业务层:用于处理业务需求,我们自己编码实现。
持久层:用于与数据库交互。
由于Mybatis是关于持久层的,后面就专注于持久层的知识。
持久层技术解决方案
- JDBC技术:是jdk官方提供的数据库交互规范。
- Spring中的JdbcTemplate:是spring提供的数据库交互工具,其在JDBC上进行了简单封装
- Apache的DButils:与JdbcTemplate相似,也是实现了简单封装。
说明:上述三者都并非框架。JDBC是一套规范,后两者是该规范的简单实现。他们都只是工具,因为其并没有形成一套完整的解决方案,我们在开发中仍需要做很多事。
myBatis概述
mybatis是一个优秀的基于java 的持久层框架,其内部封装了jdbc,使开发者只需要关注sql语句本身,而不用花费精力去处理加载驱动、创建连接、创建statement等繁杂过程。
mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中的sql的动态参数进行映射(如:Object.query("select * from tableName where id=?",db.getId())),生成最终的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
采用思想:ORM(Object Relational Mapping,对象关联映射),其解决了实体对象到数据库表中的映射问题。对jdbc进行了封装,屏蔽了jdbc api底层访问的细节,使开发者无需与其打交道,就可以完成数据库持久化操作。
Mybatis环境搭建
小细节:${}
和#{}
的区别:效果都是占位,然后等待编译时进行变量替换。区别在于#{}
替换后,会加上引号,表明该值是字符串;而${}
替换后是不加字符串的,可以用于properties文件的引用、sql语句中如ORDER BY ${}
等。
注意:当使用${}
作为sql语句时,内部是固定占位符:${value}
。
大致开发步骤:
- 1.建立maven工程并导入依赖
- 2.创建实体类和dao的接口
- 3.创建Mybatis的主配置文件:
sqlMapConfig.xml
- 4.创建每个dao对应的映射配置文件:
SubjectsDAOInterface.xml
sqlMapConfig.xml
:
SubjectDAOInterface.xml
:
注意事项:
- mybatis中,DAO的含义与Mapper相同。所以SubjectsDAO和SubjectMapper其意思相同,都代表实体类与数据库的交互。
- mybatis的映射配置文件,如上面代码
SubjectDAOInterface.xml
必须与dao接口的包结构相同。(mybatis的映射配置文件在resources
文件夹下,但其包结构也跟SubjectDAOInterface一样。) - 映射配置文件
SubjectDAOInterface.xml
中,
标签的属性namespace
的取值必须为对应dao接口的全限定名。 - 映射配置文件中,
中的子标签如:,其
id
属性必须为接口的方法名。
有了上述四条事项中的后三条,我们就可以不用编写dao接口的实现类了,mybatis会帮我们编写。
dao功能的原始实现流程:
在一个类中的main函数体中:
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//1:获取配置文件流
//import org.apache.ibatis.io.Resources;
InputStream in = Resources.getResourceAsStream("sqlMapConfig.xml");
//2:创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3:用工厂创建SqlSession对象
SqlSession ss = factory.openSession();
//4:使用SqlSession对象创建dao接口的代理对象。
SubjectsDAOInterface dao= ss.getMapper(SubjectsDAOInterface.class);
//5:使用代理对象执行方法
List subjects = dao.findAllSubjects();
for(Subjects s:subjects) {
System.out.println(s);
}
//6:释放资源
ss.close();
in.close();
}
读取配置文件说明:无论是主配置文件中的DataSource的属性解析、还是映射配置文件中的sql语句与dao接口方法关联的解析,都是用到的一种解析方法:dom4j 解析xml技术
使用注解实现配置mapper:
用注解代替映射文件。
-
主配置文件中:(得到dao接口类的全限定名)
- 将
中的
子标签的属性改成,class里面是全限定名,包以'.'分隔。
- 将
-
dao接口代码中:(得到该接口类的方法名)
-
增加注释:
类中,方法前加:@Select("sql statement")
-
-
编写@Select注释:(这步是自定义mybatis用到的)
-
首先设置该注释的生命周期和作用对象:
注释体前加:
@Retention(RetentionPolicy.RUNTIME)
、@Target(ElementType.METHOD)
-
然后在注释体内,设置一个字符串接收sql语句(在dao接口方法前注释的
@Select
的值)String value();
-
mybatis实现思路:
自定义实现的项目:ubuntu--eclipse--mybatisCustom。
大体实现方法:
- 1.通过xml解析工具,**解析主配置文件 **得到:
- 连接所需参数信息
- 映射配置文件(xml实现映射关联)
- dao接口全限定类名(注解实现映射关联)
- 2.在通过xml解析工具,解析映射配置文件或dao接口全限定名,得到:
- 被代理方法的全限定名
- sql statement(语句)
- 承载类的全限定名(用于和数据库字段相对应的那个实体类)
- 3.经第2步,已经得到了所有信息,然后是要实现动态代理和反射调用了(自定义一个实现了InvacationHandler的处理类,然后调用Proxy)。(这一步最麻烦,我没有成功,明明代码都一样,但是在invoke这一步,说参数类型不匹配,但是明明是匹配的。)
- 建立连接Connection
- 建立预处理语句,并发送执行。
- 得到结果集,将结果集映射到承载类。
- 返回代理对象。
mybatis 基础的CRUD
操作基本与前述类似。
如下,是配置映射文件:实现了基本的增查改删操作。
select last_insert_id();
INSERT into subjects(name,grade,enjoy) values(#{name},#{grade},#{enjoy});
DELETE from subjects where id = #{id};
UPDATE subjects set name = #{name},grade=#{grade},enjoy=#{enjoy} where id=#{id};
标签
主配置文件中
注意:有顺序要求,否则会报错。如下依次。
(properties, settings, typeAliases, typeHandlers, objectFactory, objectWrapperFactor, reflectorFactory, plugins, environments, databaseIdProvider, mappers)
:主标签,必须要命名空间才能用:。后面的标签都属于本标签的子标签。
:用于包含多个配置环境。属性:default
,设置一个默认的
标签。
:用于单个环境配置。属性:id
,来标识该环境配置标签。-
:事务管理标签。属性:type
,指定事务管理器类型,一般是jdbc- 子标签:
:用来注入成员变量dataSource。
- 子标签:
-
:用来配置数据源。属性:POOLED
,表示连接池。- 子标签:多个
:用来配置连接信息:驱动、url、用户名、密码。
- 子标签:多个
:用来指定外部properties文件。属性:resource
,表示文件位置。-
:用于配置包下所有类的别名。在mybatis中,由于预先设置了别名,如 resultType="int"
可以写成resulttype="INt"
,并不区分大小写。当我们自己想这样使用的,可以用该标签。
- 子标签:
:用于给某个指定类添加别名。 - 子标签:
:(*常用)用于给整个包下的所有类添加别名。属性: name="..."
- 用处:
- ①用于给dao所在包在主配置文件添加别名,然后在映射配置文件中,就可以使用别名进行引用,而不用全限定名。
- ②可以在
标签中,直接写
,可以替代
或;
- 子标签:
dao接口映射配置文件中:
:主标签。属性:namespace="path.to.daoInterface"
。-
、
、
、
:用于标识方法。属性:
id="method"
resultMap="resultMap的id"
resultType
parameterType="该方法参数类型"
-
:用于映射复杂的返回结果,如该实体类中引用别的对象,该实体类变量名与数据库中字段名不同等。子标签:
-
:用于主键映射。 -
:用于其他字段映射。 -
:用于一对一查询。 -
:用于一对多查询。
-
属性:
id
:辨识唯一标签parameterType
:当dao接口的方法需要参数时,使用该属性。value=全限定类名或者可以标识的名,如INT、int、string等resultType
:当该方法需要返回值时,用这个来标识返回值对象。value=全限定类名
OGNL表达式:(用于标签内sql语句的参数)
Object Graphic Navigation Language,对象图导航语言。
作用:通过对象的取值方法来获取数据。(写法上把get省略了)
类中写法:user.getName();
OGNL写法:user.name
mybatis写法:name //因为在parameterType里面已经提供了类型,所以省略了类。此处写法的含 //义是在如标签内部的语句,如:#{name}
对象参数:(通常用作对实体类对象的再包装,开发中,由多个对象组成一个查询条件类,来实现数据查 询)
当类对象作为方法参数时,若要用对象的成员变量的属性,如Class Condition{ User user;},传入的 参数是Condition 对象,但要使用其中的user成员变量的属性(成员变量)user.name。此时写法为: #{user.name}
数据表字段与实体类属性不同时:
当数据库的字段名和实体类的成员变量名不一样时(当然最好一样),可以使用两种方式实现查询结果正常映射到实体类:
-
sql语句中修改:在如:
(运行效率更好)
对应关系:id--myId、name--myName、grade--myGrade
修改为:
subjects
-
配置标签修改:可以通过
标签修改:(开发效率稍高)
配置properties文件
可以在resourese文件夹下创建properties文件,其中有多个值,每个值为{key、Value}映射形式:name=sun。
主配置.xml文件使用properties文件
配置:通过在 Configuration
主标签下添加
标签,然后写 属性resource=" ''
。其指为相对地址,如果放在resource下,则直接写文件名.properties。如:
使用:然后在后面的子标签如
中的子标签
,即可直接使用${key}
来获得properties文件中的value的值。
mybatis 连接池配置
三种配置方式:
-
主配置文件中的
标签属性:
type
的取值:①POOLED :采用传统的javax.sql.DataSource规范(接口)中的连接池,mybatis有对其的实现类。
②UNPOOLED :也是有实现了上述规范的实现类,但是没有连接池概念。连接是用时生成,关闭就真正销毁了。
-
③ JNDI :采用服务器提供的 JDNI 技术实现,来获取DataSource对象,不同服务器能拿到的DataSource不一样。如 tomcat服务器采用dbcp连接池。
注意:如果不是web或maven的war工厂,不能使用。
POOLED取连接过程:
概述:mybatis使用PooledDataSource
类来实现 java 的DataSource
规范,其内部步骤大概如下:
- ①要新建连接时,看空闲池里是否有连接,若有,则取出一个返回。
- ②若空闲池没有连接,则查看活跃池,若活跃池中连接数未达到最大数量,则新建一个连接,并放入活跃池,并返回。
- ③若活跃池已达到最大数量,则从其中选择最先进来的(最老的)那个连接,进行适当加工,并返回。
Sql语句和映射配置文件
说明:查询语句有条件时,可以用配置文件中的标签进行辅助完成。
映射配置文件中:
下述都是在标签的内部:
:用了该标签,可以省略sql语句中的where 1=1(当多个与、或条件时),子标签
:属性为:test="condition"
。用作条件语句,当不满足条件时,该标签内部的子标签和语句不会被执行。注意,条件语句中不能用&&、||
,要用and、or
代替-
:主要用于集合类型参数,配合实体封装类参数进行使用。属性:
-
collection
:集合属性名 -
open
:一般为:and id in(
-
close
:一般为:)
-
item
:该属性的值如:id,与内部相对应:#{id}。 -
separator
:每个id中的分隔符:,
-
mybatis多表查询
步骤:
①mysql中建立两个表(暂时以2代多)
②在项目中建立两个实体类、及其dao接口类
③配置两个dao接口的映射文件
④测试、实现配置
一对一:(包含多对一)
适用:①一对一对象;②一对多对象中,以”多“为主体,去包含唯一的“一”。
说明:通常是一对一的,如人和身份证。或者是用户和银行卡中,一张银行卡只能对应一个用户。
resultMap中标签:
-
:- 属性:
javaType
:填写类中引用对象的类型。
- 属性:
因为一对多,如一个study有多个subjects。但是一个subject只对应一个study,所以可以放在一起
一对多:
说明:如一个学生,有多门学科成绩。
resultMap中标签:
-
:- 属性:
ofType
:填写集合中的元素类型,如list中的E。
- 属性:
dao映射配置文件:
注意:user是主表,subjects是从表。
多对多
项目:ubuntu -- eclipse -- mybatisMany4Many
说明:如一个用户,其可以多个爱好。而多个人,可以有同一个爱好。如此,用户库和爱好库则是多对多。
sql语句:(两表为例)
- 首先需要一个中间表user-enjoy,其数据项为:另两表的主键(如id)。
- 然后是sql语句形如:
Select u.*,e.* from user u Left outer join user-enjoy ue on u.id=ue.uid LEFT OUTER JOIN enjoy e on e.id=ue.eid;
注意:编写sql语句中,多次将最后的条件子句中,别名写成了原名,这会报错。
下述代码:User 和 Role类,多对多查询。Role的dao接口省略。
JNDI
JNDI(Java Naming and Directory Interface,java命名和目录接口),是sun公司提供的一种标准的java命名系统接口。
说明:其打包方式为war(web archive),用于网页应用。具体还是要找专门资料
mybatis缓存
延迟加载
问题:在一对多中,当我们有一个用户,他有100个账户,在查询用户的时候,要不要把关联的账户查出来?查询账户时,要不要把关联用户查出来?
- 查用户时:应该延迟加载,即真正使用账户数据是才加载,不用的时候不查询。延迟加载也叫按需加载或懒加载。
- 查账户时:可以立即加载。即不管用不用,只要一调用方法,马上发起查询。
四种表关系:
一对多、多对多:通常使用延迟加载。
一对一、多对一:通常使用立即加载。
实现方式:无论是一对多还是多对一,其实都类似,就是在
中的
或
中,引用其他映射配置文件的语句。
X对一:
步骤:
首先,两个实体类已经有了,并且类中包含了对方的引用(成员变量)。
然后,dao接口类中,都写好方法。
-
在主配置文件中,显式开启延迟记载:
-
在映射配置文件中:
如:subject调用findAll时,不会加载user。只有用到时,才会通过
得到对应user对象。注意:此时
中,column
属性不可省略了。 还有
和
似乎都有顺序要求(
中)。放在其他位置会报错.
X对多:
实现:与X对一类似,区别:
-
变成
,及其内部的属性:javaType
变成ofType
-
column="id"
,且在从表中,应该要有对应的字段,标识该数据属于主表中指定id的。 - 还有一些就是SQL语句,和两个实体类结构的小问题了。
缓存
说明:内存用作数据库数据的缓存
适用:
- 经常查询且不经常改变的
- 数据正确与否对最终结果影响不大的
不适用:
- 经常改变的数据(不可以使用写回的策略么?不行,因为可能存在多个线程或多台主机同时操作)
- 数据的正确与否对最终结果影响很大的。(如:商品的库存、银行的汇率、股市的牌价)
mybatis中的一级、二级缓存
一级缓存:
说明:SqlSession对象里的缓存。(一级缓存是默认开启,不需要手动设置)
SqlSession在用户查询后,会缓存结果,形成一个Map结构,当再次查询同一个对象时,mybatis会先去查看是否SqlSession中是否有对应的值,如果有则返回,不需要再次访问数据库。
清空:
SqlSession.clearCache()
或者关闭sqlSession之后,再新建:ss=SqlSessionFactory.openSession();
SqlSession自动清空:
时机:当调用SqlSession的commit、update、insert的时候,会自动清空缓存。
注意:由于其机制,可能会导致缓存没有及时更新。如果并没有用该SqlSession进行更新、提交等操作,比如:
- user = daoProxy.findbyone();
- 暂停,并手动修改数据库信息。
- user2 = daoProxy.findbyone();
- user == user2 结果是true。因为他不会去访问数据库,user2是从缓存中提取的旧结果,并不是最新的数据。
二级缓存:
说明:SqlSessionFactory对象里的缓存。(需要手动设置开启)
SqlSessionFactory有一个公共缓存区,可以被其生成的任何SqlSession共享。只要一个SqlSession进行了Commit,就会将如查询得到的信息缓存在SqlSessionFactory内部。
当其新建或已建的SqlSession进行同样的查询时,就可以从其内部取数据,而不需要访问数据库。
开启:
默认是开启,设置地方:
-
主配置文件:
-
使用dao映射配置文件.xml:
-
使用注解配置映射:
注解dao接口,
@CacheNamespace(blocking=true)
即可。
注意:
- 得到的实体类对象的值是一样的,但是不同的对象,如obj1==obj2:false。
- (*)只有其他SqlSession提交或关闭之后,才会缓存到SqlSessionFactory中,若没有提交,则没有缓存。
mybatis注解开发
说明:注解与映射文件配置(路径同于dao接口),仅可以存在一个。
大致开发步骤与普通一样,仅是主配置文件中,
配置的是dao接口的地址。
然后在dao接口中,注释方法,并传入sql语句。即可。
项目:ubuntu--eclipse--mybatisAnnotation
关于@Results、@ResultMap:
-
当实体类名和数据库名不同时,如果不在sql中用别名,则需要配置映射,如下。
@Select("select * from user;") @Results(id="userMap",value= { @Result(id=true,property="uid",column="id"), @Result(property="name",column="name"), @Result(property="sex",column="sex"), @Result(property="address",column="address"), @Result(property="city",column="city") }) public List
findAll();
注解:
-
@Results
:两个属性:id
:唯一标识该Results;value
:包含多个@Result
注解,每个注解对应一个字段的映射。 -
@ResultMap
:用于其他方法注解,引用一个@Results。其值标准格式为:value={"id1",”id2”...}
。
多表查询注解
多对一:
在@Results()
中,加入属性:@Result(property="",column="", one=@One(select="path.to.method", fetchType=FetchType.EAGER))
下面代码是UserDAOInterface
,User
实体类中包含成员变量Role role
。
@Select("select u.*,ur.roleId from user as u LEFT JOIN user_role as ur on u.id=ur.userId;")
@Results(id="userMap",value= {
@Result(id=true,property="uid",column="id"),
@Result(property="name",column="name"),
@Result(property="sex",column="sex"),
@Result(property="address",column="address"),
@Result(property="city",column="city"),
@Result(property="role",column="roleId",javaType=Role.class,
one=@One(select="com.yinghuo.mybatisAnnotation. dao.RoleDAOInterface.findOneById", fetchType=FetchType.EAGER)
)
})
public List findAll();
多对多:
User
实体类中包含成员变量List
。
说明:在userDAOInterface
中与多对一类似,区别:
@Select()
中语句变为:@Select("SELECT * from user;")
one=@One(...)
变成many=@Many(...)
@One()
中,method变成...findByUserId
;其根据user_role表,和user提供的id,对应找到user_role中所有的数据项,提取roleId返回role中的多个数据项。-
以下是RoleDAOInterface.findByUserId:
@Select("SELECT r.* from role r,user_role ur where ur.userId = #{para} and ur.roleId=r.roleId;") List
findByUserId(int userId);
缓存
说明:一级缓存是自动开启的。而二级缓存需要设置。
log4J
properties文档:(尽量放在resources下)
log4j.rootLogger=debug, stdout, R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=example.log
log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=5
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014
\u7248\u6743\u58F0\u660E\uFF1A\u672C\u6587\u4E3ACSDN\u535A\u4E3B\u300C\u5C31\u662F\u4E8C\u4E8C\u4E8C\u4E8C\u5A77\u300D\u7684\u539F\u521B\u6587\u7AE0\uFF0C\u9075\u5FAA CC 4.0 BY-SA \u7248\u6743\u534F\u8BAE\uFF0C\u8F6C\u8F7D\u8BF7\u9644\u4E0A\u539F\u6587\u51FA\u5904\u94FE\u63A5\u53CA\u672C\u58F0\u660E\u3002
\u539F\u6587\u94FE\u63A5\uFF1Ahttps://blog.csdn.net/qq_34474324/article/details/98874675
错误收集
Result Maps collection does not contain value for ...
- 说明:在映射配置文件中,有一个映射配置文件的
select
标签中属性resultType
写成了resultMap
- 解决方式:检查所有的配置映射文件(通常上述错误描述for后面,会标出该错误发生的dao接口,然后去该接口配置文件看),看看是不是有写错的。