练习
N 字形变换
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入:s = "PAYPALISHIRING", numRows = 3
输出:"PAHNAPLSIIGYIR"
示例 2:
输入:s = "PAYPALISHIRING", numRows = 4
输出:"PINALSIGYAHRPI"
解释:
P I N
A L S I G
Y A H R
P I
示例 3:
输入:s = "A", numRows = 1
输出:"A"
提示:
1 <= s.length <= 1000
s 由英文字母(小写和大写)、',' 和 '.' 组成
1 <= numRows <= 1000
class Solution {
public String convert(String s, int numRows) {
if(numRows == 1)
return s;
int len = Math.min(s.length(), numRows);
String []rows = new String[len];
//为了初始化,做字符串拼接,如果不初始化就是undefined
for(int i = 0; i< len; i++) rows[i] = "";
int loc = 0;
boolean down = false;
for(int i=0;i<s.length();i++) {
rows[loc] += s.substring(i,i+1);
if(loc == 0 || loc == numRows - 1)
down = !down;
loc += down ? 1 : -1;
}
String ans = "";
for(String row : rows) {
ans += row;
}
return ans;
}
}
自从开始写算法题,基本上没几个是自己做出来的
说说一开始我自己的想法,看到这题后想的是:这不是竖着来再横着读嘛,简单
简单个p,想了半小时才想出来怎么写,但也仅限于第一行,我就想着要不要整个二维数组,利用对角线一个一个的填上去,最后横着遍历。
想太多,人大神的代码有更好的方法,一个string就够了,根据numRows的数量定义string,这样的一维数组只需要遍历s记录到对应的位置就行,例如题解1给的例子,最终结果就是
string[0]=pahn string[1]=aplsiig string[2]=yir
然后让他们遍历拼接就行了,不用我想的那么复杂,反正又没说把字符串的顺序遍历出来。
在解决numRows来回增减问题,我想着是做一个判断,当它=0或者它=numRows-1时,变成递增或者递减,但是怎么实现的,我就不写了,头疼半天还有一些问题(因为我一开始想的是二位数组),然后大神代码就告诉我,判断可以做一个开关即可
所以
代码思路
1.判断s和numRows的长度(实际上就numRows就行,为了节省空间),选出最短的,初始化rows
2.初始化完成后,遍历s,做判断,当指针=0时,递增,指针=numRows-1时,递减。这里用的down做判断,当满足时取反,根据down的false和true取值,true时取1 false取-1 并给指针做递增递减法。通过rows[指针] +=s.substring(i,i+1)来拼接字符串
3.因为rows的字符串拼接,所以最终遍历时row会横向遍历rows,所以最终返回ans
八股
集群环境下保证id不重复
原来的Synchronized + Lock只能锁单机,也就是只能在一个JVM环境下
而在分布式+集群的环境下,变成了N对N的关系,在并发的环境下,如果使用UUID或者自增ID,就可能出现ID重复的问题,因此在集群下的环境下,对JVM进行加锁,这就是分布式锁。
Zookeeper实现的分布式锁
因为Zookeeper在创建节点的时候,需要保证节点的唯一性,也就是实现原理就是,每次一个线程获取到了锁,那就在Zookeeper上创建一个临时节点,但用完锁之后,在把这个节点删除掉,
创建锁
多个JVM服务器之间,同时在zookeeper上创建相同一个临时节点,因为临时节点路径是保证唯一,只要谁能创建节点成功,谁就能获取到锁。
没有创建成功节点,只能注册个监听器监听这个锁并进行等待,当释放锁的时候,采用事件通知给其它客户端重新获取锁的资源。
这时候客户端使用事件监听,如果该临时节点被删除的话,重新进入获取锁的步骤。
释放锁
Zookeeper使用直接关闭临时节点session会话连接,因为临时节点生命周期与session会话绑定在一块,如果session会话连接关闭,该临时节点也会被删除,这时候客户端使用事件监听,如果该临时节点被删除的话,重新进入到获取锁的步骤。
单机环境下的锁
对于单进程的并发场景,我们可以使用synchronized关键字和Reentrantlock等
当我们生成订单号时,假设建好了生成订单号的工具类及调用工具类的服务,当我们假设用户端循环50次生成订单,在获取线程的时候发现,会出现重复订单号,这时候就需要在service用锁来进行解决
//lock 应该申明为类的成员变量
private Lock lock = new ReentrantLock();
public String getOrderNumber() {
lock.lock();
try {
return orderNumberCreateUtil.getOrderNumber();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return null;
}
分布式环境下的锁
对于 分布式场景,我们可以使用分布式锁。
在模板模式(Template Pattern)设计模式中,用一个抽象类公开定义了执行它的方法的方式、模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行
意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构,即可重定义该算法的某些特定步骤:
主要解决:一些方法通用,却在每个子类都重新写了这一方法
何时使用:在一些通用的方法
如何解决:将这些通用算法抽象出来
关键代码:在抽象父类中实现通用方法,其它步骤下放到子类中实现
优缺点
- 封装不变部分,扩展可变部分
- 提取公共代码,便于维护
- 行为由父类控制,子类实现
- 缺点是:每一个不同的实现,都需要一个子类来实现,导致类的个数增加,使得系统变庞大
使用场景及实例
- 有很多子类共有的方法,且逻辑相同
- 重要的、复杂的方法,可以考虑模板方法
- spring中对Hibernate的支持,将一些定好的方法封装起来,比如开启事务,获取Session,关闭Session,程序要不需要重复写那些已经规范好的代码,直接丢一个实体就可以保存。
用代码层次的方面来说
先定义一个锁接口,接口里面有上锁方法和未上锁方法
接着实现锁接口的模板,再定义两个抽象方法,一个是尝试锁,一个是等待锁,并且上锁方法会进行尝试锁,如果获取失败就转等待锁,接着重新调用上锁方法。
接着分布式锁继承上述模板,尝试锁是boolean的,用来判断节点释放存在,存在返回false,不存在返回true。等待锁需要加监控,用来检查lock节点释放
接着就可以通过ZkDistributedLock进行加锁,eg: lock(接口) lock1 = new ZkDistributedLock(继承模板的类)
lock1.lock(接口的上锁方法名)
然后在使用多个线程进行操作,而且是在线程里面实例化对象,来进行创建,最终保证每个对象再获取订单的时候,都是唯一的
mybatis_plus学习第一天
mybatis_plus特性
![](https://i-blog.csdnimg.cn/blog_migrate/99983414ac549a4e02416d9efafcf9ca.png)
CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `mybatis_plus`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
简单使用
pom
<dependencies>
<!-- mybatis-plus启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- lombok简化实体类开发 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
yml
MySQL5.7版本的url:
jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
MySQL8.0版本的url:
jdbc:mysql://localhost:3306/mybatis_plus?
serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
spring:
# 配置数据源信息
datasource:
# 配置数据源类型
type: com.zaxxer.hikari.HikariDataSource
# 配置连接数据库信息
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
username: root
password: 123456
实体类
@Data
//@NoArgsConstructor
//@AllArgsConstructor
//@Getter
//@Setter
//@EqualsAndHashCode 一个data注解包括这五个
主启动
@MapperScan("com.atguigu.mybatisplus.mapper")
//或者自己创建一个config类 让springboot去自己扫描
//@Configuration
//@MapperScan({"com.atguigu.mybatisplus.alibaba.mapper"})
dao或者mapper
@Repository
//将一个类或接口标记成持久层组件-仓库
public interface UserMapper extends BaseMapper<User>
//继承mybatisplus提供的sql映射 BaseMapper<实体类>
test
@Autowired
private UserMapper userMapper;
@Test
public void testSelectList(){
//通过条件构造器查询一个list集合,若没有条件,则可以设置null为参数
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
//经测试发现 即使mapper或者dao文件中 仅仅是继承bashmapper就可以查询数据库语句 即使usermapper爆红也能查询
日志功能
yml加入
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
basemapper
public interface BaseMapper<T> extends Mapper<T> {
/**
* 插入一条记录
* @param entity 实体对象
*/
int insert(T entity);
/**
* 根据 ID 删除
* @param id 主键ID
*/
int deleteById(Serializable id);
/**
* 根据实体(ID)删除
* @param entity 实体对象
* @since 3.4.4
*/
int deleteById(T entity);
/**
* 根据 columnMap 条件,删除记录
* @param columnMap 表字段 map 对象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根据 entity 条件,删除记录
* @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where
语句)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 删除(根据ID 批量删除)
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends
Serializable> idList);
/**
* 根据 ID 修改
* @param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity);
/**
* 根据 whereEntity 条件,更新记录
* @param entity 实体对象 (set 条件值,可以为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成
where 语句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER)
Wrapper<T> updateWrapper);
/**
* 根据 ID 查询
* @param id 主键ID
*/
T selectById(Serializable id);
/**
* 查询(根据ID 批量查询)
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends
Serializable> idList);
/**
* 查询(根据 columnMap 条件)
* @param columnMap 表字段 map 对象
*/
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object>
columnMap);
/**
* 根据 entity 条件,查询一条记录
* <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常
</p>
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
List<T> ts = this.selectList(queryWrapper);
if (CollectionUtils.isNotEmpty(ts)) {
if (ts.size() != 1) {
throw ExceptionUtils.mpe("One record is expected, but the query
result is multiple records");
}
return ts.get(0);
}
return null;
}
/**
* 根据 Wrapper 条件,查询总记录数
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T>
queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
* <p>注意: 只返回第一个字段的值</p>
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录(并翻页)
* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
<P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER)
Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录(并翻页)
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类
*/
<P extends IPage<Map<String, Object>>> P selectMapsPage(P page,
@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
test
@Test
public void testInsert(){
//实现新增用户信息
//INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
User user = new User();
//user.setId(100L);
user.setName("张三");
user.setAge(23);
user.setEmail("zhangsan@atguigu.com");
int result = userMapper.insert(user);
System.out.println("result:"+result);
System.out.println("id:"+user.getId());
}
@Test
public void testDelete(){
//通过id删除用户信息
//DELETE FROM user WHERE id=?
/*int result = userMapper.deleteById(1492767055210991617L);
System.out.println("result:"+result);*/
//根据map集合中所设置的条件删除用户信息
//DELETE FROM user WHERE name = ? AND age = ?
/*Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
map.put("age", 23);
int result = userMapper.deleteByMap(map);
System.out.println("result:"+result);*/
//通过多个id实现批量删除
//DELETE FROM user WHERE id IN ( ? , ? , ? )
List<Long> list = Arrays.asList(1L, 2L, 3L);
int result = userMapper.deleteBatchIds(list);
System.out.println("result:"+result);
}
@Test
public void testUpdate(){
//修改用户信息
//UPDATE user SET name=?, email=? WHERE id=?
User user = new User();
user.setId(4L);
user.setName("李四");
user.setEmail("lisi@atguigu.com");
int result = userMapper.updateById(user);
System.out.println("result:"+result);
}
@Test
public void testSelect(){
//通过id查询用户信息
//SELECT id,name,age,email FROM user WHERE id=?
/*User user = userMapper.selectById(1L);
System.out.println(user);*/
//根据多个id查询多个用户信息
//SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
/*List<Long> list = Arrays.asList(1L, 2L, 3L);
List<User> users = userMapper.selectBatchIds(list);
users.forEach(System.out::println);*/
//根据map集合中的条件查询用户信息
//SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
/*Map<String, Object> map = new HashMap<>();
map.put("name", "Jack");
map.put("age", 20);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);*/
//查询所有数据
//SELECT id,name,age,email FROM user
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
/*Map<String, Object> map = userMapper.selectMapById(1L);
System.out.println(map);*/
}
自定义sql
resources下创建mapper与java里面的dao或者mapper对应名字 自己写自己的
eg:
<?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">
<mapper namespace="com.atguigu.mybatisplus.mapper.UserMapper">
<!--Map<String, Object> selectMapById(Long id);-->
<select id="selectMapById" resultType="map">
select id,name,age,email from user where id = #{id}
</select>
<!--Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);-->
<select id="selectPageVo" resultType="User">
select uid,user_name,age,email from t_user where age > #{age}
</select>
</mapper>
另外 yml文件里面默认会去找resources下的mapper
想自己改就自己添加下面的属性修改
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: xxx
通用service
说明 :通用 Service CRUD 封装 IService 接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,泛型 T 为任意实体对象建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承Mybatis - Plus 提供的基类官网地址: https://baomidou.com/pages/49cc81/#service-crud-%E6%8E%A5%E5%8F%A3ServiceMyBatis-Plus 中有一个接口 IService 和其实现类 ServiceImpl ,封装了常见的业务层逻辑详情查看源码 IService 和 ServiceImpl
service
public interface UserService extends IService<User> {
}
service/impl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
test 老师只演示两个 剩下的自己翻源码
@Autowired
private UserService userService;
@Test
public void testGetCount(){
//查询总记录数
//SELECT COUNT( * ) FROM user
long count = userService.count();
System.out.println("总记录数:"+count);
}
@Test
public void testInsertMore(){
//批量添加
//INSERT INTO user ( id, name, age ) VALUES ( ?, ?, ? )
List<User> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
User user = new User();
user.setName("ybc"+i);
user.setAge(20+i);
list.add(user);
}
boolean b = userService.saveBatch(list);
System.out.println(b);
}
常用注解
解决数据库表与实体类表名不一致@TableName
在实体类前面添加注解 //设置实体类所对应的表名 @TableName("t_user")
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #设置myBatis-plus全局配置 global-config: db-config: #设置实体类表的统一前缀 table-prefix: t_
在实体类中 uid 属性上通过 @TableId 将其标识为主键,即可成功执行 SQL 语句/将属性所对应的字段指定为主键 //@TableId注解的value属性用于指定主键的字段 //@TableId注解的type属性设置主键生成策略 //mybatis-plus默认雪花算法 如果我想用mysql自动递增呢 //@TableId(value = "uid", type = IdType.AUTO) @TableId("uid") private Long id;如果不想使用默认的雪花算法 不仅上面改 在mysql也要改自增
IdType.ASSIGN_ID (默认)基于雪花算法的策略生成数据 id ,与数据库 id 是否设置自增无关IdType.AUTO使用数据库的自增策略,注意,该类型请确保数据库设置了 id 自增,否则无效如果手动设置id了 是不会进行任何策略的
yml设置全局配置 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #设置myBatis-plus全局配置 global-config: db-config: #设置实体类表的统一前缀 table-prefix: t_ #配置MyBatis-Plus的主键策略 id-type: auto
![](https://i-blog.csdnimg.cn/blog_migrate/67cb0a1114191106a6649649a57ffef0.png)
雪花算法
![](https://i-blog.csdnimg.cn/blog_migrate/2dab077551ad331c4a2117dd401f80a6.png)
主键自增
取模
雪花算法
②优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。
若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格
例如实体类属性userName,表中字段user_name
此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格
相当于在MyBatis中配置
若实体类中的属性和表中的字段不满足情况1
例如实体类属性name,表中字段username
此时需要在实体类属性上使用@TableField("username")设置属性所对应的字段名
//指定属性所对应的字段名 @TableField("user_name")
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库
中仍旧能看到此条数据记录
使用场景:可以进行数据恢复
step3:测试
测试删除功能,真正执行的是修改
UPDATE t_user SET is_deleted=1 WHERE id=? AND is_deleted=0
测试查询功能,被逻辑删除的数据默认不会被查询
SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0
总结
一天学一半确实不算多,毕竟全套课也才6小时,有一说一,确实mybatis都忘得差不多了,学者mybatisplus就想着如果要操作复杂的sql是不是得用mybatis,现在就想着重写回顾一下mybatis了,真的是不用就忘,明天再看看,反正也没上班,想学啥学啥,大不了花几天再回顾一波mybatis。