강의 | 수강완료 | |
1 | DevOps와 CI/CD의 이해 | 2022.10.26 |
2 | Jenkins를 이용한 CI/CD 자동화 도구 사용 |
'CICD > Jenkins + CI&CD' 카테고리의 다른 글
Jenkins를 이용한 CI/CD 자동화 도구 사용 (0) | 2022.10.26 |
---|---|
DevOps와 CI/CD의 이해 (0) | 2022.10.18 |
강의 | 수강완료 | |
1 | DevOps와 CI/CD의 이해 | 2022.10.26 |
2 | Jenkins를 이용한 CI/CD 자동화 도구 사용 |
Jenkins를 이용한 CI/CD 자동화 도구 사용 (0) | 2022.10.26 |
---|---|
DevOps와 CI/CD의 이해 (0) | 2022.10.18 |
목차
String jpql = "select m From Member m where m.name like '%hello%'";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();
List<Member> result = em.createQuery("select m From Member m where m.username like '%hello%'"
Member.class).getResultList();
*동적 쿼리 : 필요에 의해 직접 붙히거나 짜르는 쿼리
String qlString = "select m From Member m";
String username;
if(username != null){
String where ="where m.username like '%kim%'";
qlString + where;
}
다음과 같이 작성할 수 있다. 위의 단점을 해결 할 수 있다.
QMember m = QMember.member;
List<Member> result = queryFactory
.select(m)
.from(m)
.where(m.name.like("kim")
.fetch();
public List<Order> findAllByQuerydsl(OrderSearch ordersearch){
return queryFactory
.select(order)
.from(order)
.join(order.member, member)
.where(statusEq(orderSearch), memberNameEq(orderSerarch))
.fetch();
}
private BooleanExpression memberNameEq(OrderSearch oderSearch){
return hasText(orderSearch.getMemberName()) ? member.name.eq(orderSearch.getMemberName)): null;
}
private BooleanExpression statusEq(OrderSearch oderSearch){
return orderSearch.getMemberName() != null ? order.status.eq(orderSearch.getOrderStatus()):null;
}
도메인 분석 설계 (N:N 뿌시기) (0) | 2022.11.19 |
---|---|
연관관계 매핑 기초 (0) | 2022.08.22 |
엔티티 매핑 (0) | 2022.08.06 |
[JPA] JPA 영속성 컨텍스트 (0) | 2022.05.05 |
[JPA] Batch Insert (0) | 2022.05.05 |
목차
객체와 테이블 연관관계 차이를 이해
객체의 참조와 테이블의 외래 키 매핑
객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다.
(연관관계가 없는 객체)
Member는 N이며 TEAM은 1이다.
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
private String username;
//연관관계 매핑
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
//연관관계 설정
public void setTeam(Team team) {
this.team = team;
}
//Getter, Setter ...
}
@Entity
public class Team {
@Id
@Column (name = "TEAM_ID")
private String id;
private String name;
//Getter, Setter ...
}
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//Member
Member member = new Member();
member.setUsername("member1");
member.setTeamId(team.getId()); //여기서 이상함
em.persist(member);
//문제가 많은 find하기
Member findMember = em.find(Member.class, member.getId());
Long findTeamId = findMember.getTeamId();
Team findTeam = em.find(Team.class, findTeamId);
위의 코드에서 문제점은 findMember를 memberId로 조회하고 그 조회한 내용 중에 TeamId를 가져와서 Team을 또 조회한다.
Join으로 협력관계가 있는게 아닌 Select를 두 번 하는 꼴
객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
위의 모델링과 다른 점은 TeamId가 아닌 Team을 모델링 했다는 점이다.
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
private String username;
//연관관계 매핑
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team; //가장 중요
//연관관계 설정
public void setTeam(Team team) {
this.team = team;
}
//Getter, Setter ...
}
Member 입장 에서 N이고 Team이 1이기 때문에 ManyToOne이다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//Member
Member member = new Member();
member.setUsername("member1");
//member.setTeamId(team.getId()); //여기서 이상함
member.setTeam(team);
em.persist(member);
//문제가 많은 find하기
Member findMember = em.find(Member.class, member.getId());
//Long findTeamId = findMember.getTeamId();
//Team findTeam = em.find(Team.class, findTeamId);
Team findTeam = findMember.getTeam();
위의 findMember는 select 문이 join문으로 알아서 해준다.
도메인 분석 설계 (N:N 뿌시기) (0) | 2022.11.19 |
---|---|
객체지향 쿼리 언어1 - 기본 문법 (0) | 2022.09.24 |
엔티티 매핑 (0) | 2022.08.06 |
[JPA] JPA 영속성 컨텍스트 (0) | 2022.05.05 |
[JPA] Batch Insert (0) | 2022.05.05 |
목차
서버란?
물리서버와 논리 서버로 구성
컴퓨터 자체(하드웨어)를 가리크는 물리 서버
컴퓨터에서 동작하고 있는 소프트웨어 논리 서버 (웹서버, DB서버)
공유형은 단순형과 달리 일부 계층에서 상호 접속이 이루어진다.
아키텍처 트랜드
오픈화(분산) -> 가상화/클라우드(집중) -> 엣지 컴퓨팅(분산)
* 엣지 컴퓨팅 : 최신 키워드, 지리적으로 가까운 위치에 있는 서버로 처리하고, 처리 결과만 중앙으로보내는 아키텍처
엣지 컴퓨팅에서는 관리를 위한 수고를 줄이면서, 서버를 분산하는 것이 중요하다.
그림으로 공부하는 IT 인프라 구조 (0) | 2022.08.10 |
---|---|
도커란? (0) | 2022.03.13 |
[그림으로 공부하는 IT 인프라 구조] 인프라란 무엇일까? (0) | 2022.08.10 |
---|---|
도커란? (0) | 2022.03.13 |
목차
package jpabook.start;
import javax.persistence.*;
import java.util.Date;
@Entity
@Table (name="MEMBER")
public class Member {
@Id
@Column (name = "ID")
private String id;
@Column (name - "NAME")
private String username;
private Integer age;
//== 추가 ==
@Enumerated (EnumType. STRING)
private RoleType roleType; // 1
@Temporal (TemporalType. TIMESTAMP)
private Date createdDate; // 2
@Temporal (TemporalType. TIMESTAMP)
private Date lastModifiedDate; // 2
@Lob
private String description; //3
}
//Getter, Setter
package jpabook.start;
public enum RoleType {
ADMIN, USER
}
@Column(nullable = false, length =10)
@Table(uniqeConstraints = (@UniqueConstraint(name = "NAME_AGE_UNIQUE", columnNames ={"NAME", "AGE"})))
옵션
|
설명
|
name | 필드와 매핑할 테이블의 컬럼 이름 |
insertable, updatable | 등록, 변경 가능 여부 (기본 값 : TRUE) |
nullable(DDL) | null 가능 여부, false 설정하면 DDL 생성 시에 Not Null 조건이 붙다. |
unique(DDL) | @Table의 uniqueConstraints와 같지만 한 컬럼에 간단한 유니크 제약조건을 걸 때 사용한다. |
columnDefinition(DDL) | 데이터베이스 컬럼 정보를 직접 줄 수 있다. ex) varchar(100) default 'EMPTY' |
length(DDL) | 문자 길이 제약 조건, String 타입에만 사용 (기본 값 : 255) |
precision, scale(DDL) | 소수의 자릿수 설정 (기본 값 : precision=19) |
날짜 타입(java.util.Date, java.util.Calendar) 매핑할 때 사용한다.
아래와 같이 적으면 어노테이션이 필요없다!
private LocalDate localDate;
private LocalDateTime localDateTime;
데이터베이스 BLOB, CLOB 타입과 매핑
@Lob에는 지정할 수 있는 속성이 없고, 대신에 매핑하는 필드 타입이 문자면, CLOB으로 매핑하고 나머지는 BLOB로 매핑한다.
객체 임시로 어떤 값을 넣고 싶을 때 사용하고 데이터베이스에는 반영이 안된다.
사실상 운영에선 사용하진 않고, 개인적으로 개발할때 정도 사용한다.
xml 다음과 같이 입력하면 초기 실행 시 자동으로 테이블을 생성한다.
<property name="hibernate.hbm2ddl.auto" value ="create"/>
위의 value 속성은 개발 단계마다 다르게 생성할 수있다.
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Id 적용 가능 자바 타입
sequenceName 속성의 이름으로 BOARD_SEQ 를 지정했는데 JPA는 이 시퀸스 생성기를 실제 데이터베이스 BOARD_SEQ 시퀀스와 매핑한다. sequenceName을 따로 설정하지 않으면 hibernate_sequence와 같이 자동으로 설정된다.
@Entity
@SequenceGenerator(
name = "BOARD_SEQ_GENERATOR".
sequenceName = ”BOARD_SEQ”, //매핑할 데이터베이스 시퀀스 이름
initialvalue = 1,
allocationsize = 1)
public class Board {
@IdQGeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "BOARD_SEQ_GENERATOR")
private Long id;
...
}
call next value for MEMBER_SEQ란?
영속성 컨텍스트에 의해 JPA는 항상 PK값을 알아야한다. 그래서 MEMBER_SEQ값에서 id값을 조회한다. 그러고 나서 em.persist를 해준다.
commit 하는 시점에 insert쿼리가 날라간다.
SquenceGenerator.allocationSize의 기본값이 50인 이유는 최적화 때문이다 allocationSize 값이 50이면 시퀀스를 한 번에 50 증가 시킨 다음에 1~50까지는 메모리에서 식별자를 할당한다. 이 최적화 방법은 시퀀스 값을 선점 하므로 여러 JVM이 동시에 동작 해도 기본 키 값이 충돌하지 않는 장점이 있다. 반면에 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한번에 많이 증가한다는 점을 염두해 두어야 한다. 참고로 앞서 설명한 hibernate.id.new_generator_mappings 속성을 true로 설정해야 지금까지 설명한 최적화 방법이 적용된다.
@Entity
@TableGenerator(
name = "BOARD_SEQ_GENERATOR",
table = ”MY_SEQUENCES",
pkColumnValue = ”BOARD_SEQ”,
allocationsize = 1)
public class Board {
@Id
@GeneratedValue(
strategy = GenerationType.TABLE,
generator = '' BOARD_SEQ_GENERATOR''
)
private Long id;
...
}
TABLE 전략과 최적화 TABLE 전략은 값을 조회하면서 SELECT 쿼리를 사용하고 다음 값으로 증가시키기 위해 UPDATE 쿼리를 사용한다. 이 전략은 SEQUENCE 전략과 비교해서 데이터베이스와 한번 더 통신하는 단점이 있다. TABLE 전략을 최적화하려면 TableGenerator.allocationSize를 사용하면 된다.
GenerationType.AUTO는 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다. AUTO 전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 것이다. AUTO를 사용할 때 SEQUENCE나 TABLE 전략이 선택되면 시퀀스나 키 생성용 테이블을 미리 만들어 두어야 한다.
기본 키 생성을 데이터베이스에 위임
IDENTITY 전략은 지금 설명한 AUTO INCREMENT를 사용한 예제처럼 데이터베이스에 값을 저장하고 나서야 기본 키 값을 구할 수 있을 때 사용한다.
private static void logic (EntityManager em) {
Board board = new Board(); em.persist (board);
System.out.println("board.id = " + board.getId());
}
//출력: board.id = 1
문제점 : IDENTITY 전략은 데이터를 데이터베이스에 INSERT한 후에 기본 키 값을 조회할 수 있다.
티티가 영속 상태가 되려면 식별자가 반드시 필요하다. 그런데 IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있으므로 em.persist()를 호출하는 즉시 INSERT SQL 이 데이터베이스에 전달된다. 따라서 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다. 왜냐하면 JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행합니다. 그리고 AUTO_INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있습니다.
객체지향 쿼리 언어1 - 기본 문법 (0) | 2022.09.24 |
---|---|
연관관계 매핑 기초 (0) | 2022.08.22 |
[JPA] JPA 영속성 컨텍스트 (0) | 2022.05.05 |
[JPA] Batch Insert (0) | 2022.05.05 |
JPA 소개 (0) | 2022.03.14 |
목차
닷넷 프레임워크(.NET Framework)는 마이크로소프트사에서 제공하는 윈도우 프로그램 개발 및 실행환경입니다. 네트워크 작업, 인터페이스 등의 많은 작업을 캡슐화하여 코딩의 효율성을 증대시켰습니다. .NET의 특징은 CLS(닷넷 프레임워크의 언어가 반드시 지켜야 하는 언어 스펙)을 따르는 언어라면 어떠한 언어라도 닷넷 프레임워크에서 실행 가능하며 CLR이라는 가상 기계 위에서 작동하기 때문에 플랫폼에 독립적이며 궁극적으로 프로그래머가 코딩(특히 윈도우 프로그램)을 하는데 더 편한 환경을 제공해줍니다.
2000년대 들어서며 썬마이크로시스템즈(현오라클)의 자바가 새로운 차세대 언어로 각광받으면서 마이크로소프트측에서도 자바의 장점을 수용하여 새로운 언어를 만들었는데요. 이것이 바로 C#이라는 언어입니다. C#이라는 언어와 .NET이라는 개발 프레임워크를 한데 묶어 자바진영에 대항하려고 했던것이죠. 결론적으로는 절반의 성공을 이루었지만 결과적으로 자바진영을 이기는데는 실패하였습니다.
마이크로소프트에서 배포한 C# 언어가 현시대에서는 많이 소외된 부분이 있는점은 사실입니다. 이미 자바가 웹으로는 JSP, 모바일에서는 Android등 여러방면에서 압도하고 있는점이 사실이죠. 파이썬이 인공지능등의 4차혁명이슈를 타고 최근 급격히 상승세인 점도 큰 부담일 것입니다. 더 입지가 좁아들 수 밖에 없죠 하지만! C#의 장점도 물론 있습니다. 바로 윈도우 프로그램 개발에 있어 최적화 되어있다는 점이죠. 현시대 대부분의 컴퓨터 OS는 윈도우를 사용합니다. 윈도우는 마이크로소프트사에서 제작하였고 그렇기에 C#는 윈도우 프로그램개발에있어 최적의 효율을 자랑합니다. 대부분의 윈도우 프로그램개발자들이 .NET프레임워크를 사용하고 있습니다. C#언어의 입지가 좁아짐에 따라 C#개발자들의 입지도 좁아진것도 사실이긴 하나 그와 동시에 요새 프로그래밍 교육과정도 대부분 자바중심으로 이루어져있어 자바 개발자들이 공급이 많이 되는 추세입니다. 윈도우를 이길만한 OS가 나오지 않는 이상 C#의 사용처는 분명히 있을것입니다. 앞으로는 자바개발자들이 분명 더 경쟁력이 있는 것은 부정할 수 없으나 C#개발자들이 희소성이 생기는 시점도 분명히 올 것 이라고 저는 생각하고 있습니다.
마이크로소프트는 닷넷 프레임워크를 출시하면서 잘 표현할 수 있는 언어가 필요했고, 그에 따라 탄생한 언어가 C#입니다.
C#은 닷넷을 위해 태어났고, 닷넷과 함께 발전해 나갑니다. C#의 시작은 Visual Studio.Net 2002,와 함께 릴리즈된 C# 1.0 입니다. 현재는 C# 8.0 까지 나온 상태입니다.
C#은 닷넷 프레임워크를 기반으로 IL코드(C#코드와 기계어 사이의 중간언어)를 생성하는 컴파일러에 불과합니다. 따라서 '문법적인 요소'를 제외하고는 닷넷 프레임워크의 영역에 해당하기 때문에 C#을 배운다는 것은 즉, 닷넷 프레임워크를 배운다는 의미입니다. 이말은 C#을 배우면 응용 프로그램의 유형에 따라 다양한 선택권을 가지게 됩니다.
닷넷 프레임워크 - Windows Form, WPF
닷넷 코어 - 콘솔, 웹 앱, 클라우드, 윈도우 폼
자마린 플랫폼 - 모바일(IOS, Android) 앱, 윈도우 스토어 앱
Unity 게임 엔진 플랫폼 - 모바일 게임 개발
.NET 언어로 작성된 프로그램을 실행하고 관리하는 실행환경이며 Java의 Virtual machine과 비슷합니다.
CLR은 .NET에서 동작하는 프로그램을 적재하고, 프로그램의 동적 컴파일, 프로그램의 실행, 메모리관리 (Garbage Collection), 프로그램의 예외처리, 언어 간의 상속 지원, COM과의 상호 운영성 지원 등을 가능하게 합니다. .NET 언어 즉, C#뿐만 아니라 위에서 나열하였던 VB .NET 등 .NET 표준을 따르는 다양한 언어들은 CLR을 통해 프로그램을 실행할 수 있게 됩니다.
이것이 가능한 이유는 Intermediate Language (IL, 중간언어) 라는 기계어로 변환하기 쉬운 중간 단계의 언어로 .NET에서 실행되기 위해 IL 형태로 컴파일을 하는데 IL을 기계어로 바꾸는 번역기만 제공되면 어떤 플랫폼에서도 실행가능합니다. JIT (Just-In-Time) 컴파일러를 통해 IL을 동적으로 컴파일하는데 .NET 에서는 이와같이 프로그램을 2번 컴파일 합니다. Assembly는 이 때 IL로 컴파일된 결과 파일들을 패키징 한 것을 말합니다.
또 .NET 언어가 지켜야 하는 스펙으로 Common Language Specifications (CLS)로 다른 .NET 언어로 작성된 것도 호환되어 동작 가능하게 합니다. 이 과정에서 Common Type System(CTS)라는 .NET 언어마다 Data type이 다를 수 있는데 이를 언어나 시스템 환경에 관계없이 동일한 Data type을 유지하기 위한 규약이 사용되기도 합니다.
간단히 요약하면, C# 컴파일러는 우리가 작성한 코드를 IL로 작성된 실행파일을 만들게 되고, 이 파일을 실행시키면 CLR이 IL을 읽어 다시 OS에 이해할수 있는 코드로 컴파일하여 실행시킵니다. 이렇게 서로 다른 언어가 만나는 지점이 IL언어이고, CLR이 다시 자신이 설치되어 있는 플랫폼에 최적화시켜 컴파일한 후 실행하게 됩니다. 이를 통해 플랫폼에 최적화된 코드를 만들어 내는 장점이 있으나, 실행시에 이루어지는 컴파일이 부담일 수 있습니다.
출처: https://2-nan.tistory.com/40 [이난의 개발자 블로그:티스토리]
https://laboputer.github.io/csharp/2018/06/07/create-a-installer/
목차
결합 인덱스란 두 개 이상의 컬럼을 합쳐서 인덱스를 만드는 것을 말합니다. 주로 단일 컬럼으로는 나쁜 분포도를 가지지만 여러 개의 컬럼을 합친다면 좋은 분포도를 가지고, Where절에서 AND 조건에 많이 사용되는 컬럼들을 결합 인덱스로 구성합니다.
1. where절에서 and 조건으로 자주 결합되어 사용되면서 각각의 분포도 보다 두 개 이상의 컬럼이 결합될 때 분포도가 좋아지는 컬럼들
2. 다른 테이블과 조인의 연결고리로 자주 사용되는 컬럼들
3. order by에서 자주 사용되는 컬럼들
4. 하나 이상의 키 컬럼 조건으로 같은 테이블의 컬럼들이 자주 조회될 때
결합 인덱스를 만들 때 결합 인덱스를 구성하는 컬럼들의 배열 순서는 아주 중요하기에 신중하게 결정하여야 합니다. 컬럼의 순서를 잘못 배열하면 결합 인덱스의 발동 확률이 매우 낮아질 수 있기 때문입니다. 만약 select 문의 where절에 결합 인덱스의 첫 번째 컬럼을 조건에 사용하였다면 그 질의문은 결합 인덱스를 사용할 수 있습니다. 하지만 개발자가 결합 인덱스의 두번째 컬럼만을 where 절에 조건으로 사용하고 결합 인덱스를 사용하고자 했다면 실행계획은 인덱스를 사용하지 못합니다. 따라서 쿼리문 작성 시 결합 인덱스를 사용하고자 한다면 반드시 결합 인덱스의 컬럼 중 선행하는 컬럼부터 조건에 지정하여 사용하여야 합니다. 조건은 컬럼 전체를 순서대로 사용할 수도 있고, 아니면 선행하는 일부 컬럼을 순서대로 사용할 수 있습니다.
1. where절 조건에 많이 사용되는 컬럼이 우선시
2. Equal('=')로 사용되는 컬럼 우선
3. 분포도가 좋은 컬럼을 우선
4. 자주 이용되는 순서대로 결합 인덱스 컬럼의 순서 결정
create index emp_pay_idx on emp_pay(급여년월, 급여코드, 사원번호);
emp_pay 테이블에서 급여년월, 급여코드, 사원번호 컬럼으로 emp_pay_idx라는 결합 인덱스를 생성하였습니다.
select * from emp_pay where 급여년월 = '202107';
select * from emp_pay where 급여년월 = '202107' and 급여코드 ='정기급여';
select * from emp_pay where 급여년월 = '202107' and 급여코드 = '정기급여' and 사원번호 = '20210401';
select 문장의 where 절에서는 다음과 같은 조건 조합에서 인덱스가 사용되게 됩니다.
결합 인덱스도 일반적인 인덱스와 마찬가지로 데이터들이 정렬되어 보관되기 때문에 소수의 데이터를 빠르게 찾는 것에는 유리하지만 아래와 같이 스캔이 많이 생기게 된다면 효율성이 떨어지게 됩니다. 아래의 예시들은 emp_pay_idx 인덱스를 사용하기는 하지만 스캔이 많이 생기는 경우로 인덱스의 효율성이 떨어지는 경우들의 예시입니다.
select * from emp_pay where 급여년월 LIKE '2021%' and 급여코드 = '정기급여';
위 조건절의 경우 결합 인덱스의 첫 번째 컬럼인 급여년월의 조건이 있더라도 Equal(=)이 아닌 범위 연산자인 LIKE '2021%' 조건을 사용했으므로, 세개의 칼럼이 모두 필요한 emp_pay_idx 인덱스를 찾을 때 두번째 칼럼인 급여코드에 대한 조건을 B*Tree에서 쉽게 찾을수가 없게 됩니다. 이는 결합 인덱스가 각 칼럼별로 정렬이 되어 있는 것이 아니라 첫번째, 두번째, 세번째 칼럼이 결합이 되어 정렬이 되어있기 때문입니다. 이때 급여코드에 대한 조건은 인덱스를 찾아가는 검색조건이 아니라 인덱스 값이 조건에 맞는지 여부를 검증하는 체크 조건이 됩니다.
select * from emp_pay where 급여년월 = '202107' and 사원번호 = '20210401';
위 조건절의 경우는 결합 인덱스의 첫번째 칼럼인 급여년월의 조건이 equal(=)이더라도 두번째 컬럼인 급여코드에 대한 조건이 없으므로 세번째 칼럼인 사원번호 조건을 검색 조건이 아닌 체크 조건으로 밖에 사용할 수 없게 됩니다. 즉 결합 인덱스에서 급여년월인 모든 데이터를 찾아서 사원번호 조건에 맞는지 일일이 확인하는 풀 테이블 스캔이 일어나고 있는 셈입니다.
[DB] Dababase Sharding 이란? (0) | 2022.05.05 |
---|---|
인덱스(Index) 란? (0) | 2022.05.05 |
인덱스 란? *** (0) | 2022.02.18 |