原始 JDBC 的操作问题分析
1. 频繁创建和销毁数据库的连接会造成系统资源浪费从而影响系统性能。
2. sql语句在代码中硬编码,如果要修改 sql 语句,就需要修改 java 代码,造成代码不易维护。
3. 查询操作时,需要手动将结果集中的数据封装到实体对象中。
4. 增删改查操作需要参数时,需要手动将实体对象的数据设置到 sql 语句的占位符。
原始 JDBC 的操作问题解决方案
1. 使用数据库连接池初始化连接资源。
2. 将 sql 语句抽取到配置文件中。
3. 使用反射、内省等底层技术,将实体与表进行属性与字段的自动映射
①添加MyBatis的maven包
②创建user数据表
③编写User实体类
④编写核心文件MyBatisConfig.xml
⑤编写映射文件UserMapper.xml 和 UserMapper接口
⑥编写测试类
mysql
mysql-connector-java
8.0.29
org.mybatis
mybatis
3.5.3
log4j
log4j
1.2.17
CREATE TABLE user
(
id
int(11) NOT NULL AUTO_INCREMENT,
username
varchar(255) DEFAULT NULL,
password
varchar(255) DEFAULT NULL,
age
int(11) DEFAULT NULL,
phone
varchar(255) DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
public class User {
private int id;
private String username;
private String password;
private String phone;
private int age;
set get 方法
}
可以参考官方文档
https://mybatis.org/mybatis-3/zh/getting-started.html
# 数据库信息 db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/linjiu?useUnicode=true&characterEncoding=utf-8
username=root
password=root
# 配置文件
# 日志文件 log4j.properties
# Global logging configuration
# ERROR WARN INFO DEBUG
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
public interface UserMapper {
List<User> selectAll();
User selectById(int id);
}
//映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis的DTD约束-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
mapper:核心根标签
namespace属性:名称空间
-->
<mapper namespace="com.linjiu.mapper.UserMapper">
<!--
select:查询功能的标签
id属性:唯一标识
resultType属性:指定结果映射对象类型
parameterType属性:指定参数映射对象类型
-->
<select id="selectAll" resultType="user">
SELECT * FROM user
</select>
<select id="selectById" resultType="com.linjiu.model.User" parameterType="int">
select * from user where id=#{id}
</select>
</mapper>
mapper.xml文件
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">
mapper>
@Test
public void main1( ) throws IOException {
//加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获得sqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//获得sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行sql语句
List<User> users = sqlSession.selectList("com.linjiu.mapper.UserMapper.selectAll");
//打印结果
System.out.println(users);
User user = sqlSession.selectOne("com.linjiu.mapper.UserMapper.selectById", 1);
System.out.println(user);
//打印结果
sqlSession.close();
}
# 事务管理器(transactionManager)
- JDBC:这个配置就是直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
- MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。
# 数据源(dataSource)
- UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
- POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。
- JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
采用 Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是我们后面进入企业的主流。
Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口定义创建接口的动态代理对象。
Mapper 接口开发需要遵循以下规范:
1、 Mapper.xml文件中的namespace与mapper接口的全限定名相同
2、 Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
3、 Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
4、 Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
@Test
public void main1( ) throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取到mapper的代理对象,代理对象直接操作数据库
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectAll();
System.out.println(users);
User user = mapper.selectById(1);
System.out.println(user);
sqlSession.close();
}
面试题:
mybatis中# 和 $的区别
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>5.0.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>5.0.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.0.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-expressionartifactId>
<version>5.0.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.0.7.RELEASEversion>
dependency>
<dependency>
<groupId>aopalliancegroupId>
<artifactId>aopallianceartifactId>
<version>1.0version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.0.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.0.7.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.0.7.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.35version>
dependency>
<dependency>
<groupId>commons-dbcpgroupId>
<artifactId>commons-dbcpartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.5version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>1.3.1version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.24version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-surefire-pluginartifactId>
<version>2.5version>
<configuration>
<skipTests>trueskipTests>
configuration>
plugin>
plugins>
build>
public class Account {
private int id;
private String name;
private double money;
set get 方法
}
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`money` decimal(11,0) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
public interface AccountMapper {
int update(@Param("name") String name,@Param("money") double money);
double queryMoney(String name);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.linjiu.dao.AccountMapper">
<select id="queryMoney" parameterType="string" resultType="double">
SELECT money from account where name = #{name}
select>
<update id="update" parameterType="account">
UPDATE account SET money = #{money} WHERE name = #{name}
update>
mapper>
说明:
@param() 当只有一个参数时,没什么好说的,传进去一个值也只有一个参数可以匹配。当存在多个参数时,传进去的值就区分不开了,这时可以考虑用
当配置文件中有多个参数的时候,parameterType该怎么写呢? 一般可以使用实体类,也可以使用map。底层还是使用了反射的原理,根据对象的字段去匹配数据库相应的字段
public interface IAccountService {
/**
* 转账功能
* @param from 主动转账的人
* @param to 收款的人
* @param money 金额
*/
void transfer(String from, String to, double money);
}
@Service
@Transactional
public class AccountServiceImpl implements IAccountService {
@Autowired
private AccountMapper accountMapper;
public void transfer(String from, String to, double money) {
// 先查询from账户的钱
double fromMoney = accountMapper.queryMoney(from);
// 对from账户进行扣钱操作
accountMapper.update(from, fromMoney-money);
//手动制造异常
System.out.println(1 / 0);
// 先查询to账户的钱
double toMoney = accountMapper.queryMoney(to);
// 对to账户进行加钱操作
accountMapper.update(to, toMoney-money);
System.out.println("支付宝到账:"+money+"元");
}
}
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/linjiu?useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.linjiu.dao"/>
bean>
<context:component-scan base-package="com.linjiu"/>
beans>
DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name = "cacheEnabled" value = "true" />
settings>
<typeAliases>
<package name="com.linjiu.model" />
typeAliases>
<mappers>
mappers>
configuration>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class AppTest {
@Autowired
private IAccountService accountService;
@Test
public void testTransfer() throws Exception {
accountService.transfer("husband", "wife", 100);
}
}
说明:
其实整合的目的就是把mapper对象放入到容器中。(从接口生成对象,底层还是反射)
注意配置文件的加载,测试类中指定了spring的配置文件, spring的配置文件中又指定了mybatis配置文件和mapper映射文件。
mybatis的缓存主要是针对查询操作的
二级缓存是 mapper 映射级别的缓存, 默认是不开启的,需要手动开启二级缓存
当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,也就是序列化
<setting name="cacheEnabled" value="true"/>settings>
1.读未提交(Read uncommitted):脏读
脏读: 一个事务在执行的过程中读取到了其他事务还没有提交的数据
Tom的账户money=0,公司发工资把5000元打到Tom的账户上,Tom的money=money+5000元,但是该事务并未提交,而Tom正好去查看账户,发现工资已经到账,账户money=5000元,非常高兴,可是不幸的是,公司发现发给Tom的工资金额不对,应该是2000元,于是迅速回滚了事务,修改金额后,将事务提交,Tom再次查看账户时发现账户money=2000元,Tom空欢喜一场,从此郁郁寡欢,走上了不归路……
2.读已提交(Read committed):
可避免 脏读 的发生。
两次读取的数据不一样,自己事务没有提交的时候可以读取别的已经提交的事务。
Tom拿着工资卡去消费,酒足饭饱后在收银台买单,服务员告诉他本次消费1000元,Tom将银行卡给服务员,服务员将银行卡插入POS机,POS机读到卡里余额为3000元,就在Tom磨磨蹭蹭输入密码时,他老婆以迅雷不及掩耳盗铃之势把Tom工资卡的3000元转到自己账户并提交了事务,当Tom输完密码并点击“确认”按钮后,POS机检查到Tom的工资卡已经没有钱,扣款失败,Tom十分纳闷,明明卡里有钱,于是怀疑POS有鬼,和收银小姐姐大打出手,300回合之后终因伤势过重而与世长辞,Tom老婆痛不欲生,郁郁寡欢,从此走上了不归路…
3.可重复读(Repeatable read):幻读
MySql默认隔离级别。自己事务没有提交的时候,读不到别的已经提交的事务。
Tom的老婆工作在银行部门,她时常通过银行内部系统查看Tom的工资卡消费记录。2019年5月的某一天,她查询到Tom当月工资卡的总消费额为80元,Tom的老婆非常吃惊,心想“老公真是太节俭了,嫁给他真好!”,而Tom此时正好在外面胡吃海塞后在收银台买单,消费1000元,即新增了一条1000元的消费记录并提交了事务,沉浸在幸福中的老婆查询了Tom当月工资卡消费明细一探究竟,可查出的结果竟然发现有一笔1000元的消费,Tom的老婆瞬间怒气冲天,外卖订购了一个大号的榴莲,傍晚降临,Tom生活在了水深火热之中,只感到膝盖针扎的痛…
4.串行化(Serializable ):
可避免 脏读、不可重复读、幻读 的发生。
事务有以下特性(ACID):
1、原子性(Atomicity)
事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
2、一致性(Consistency)
事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
3、隔离性(Isolation)
多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
4、持久性(Durability)
已被提交的事务对数据库的修改应该永久保存在数据库中