There are a number of approaches using which you can upgrade a Contract1 to Contract2, keeping its state(data & balance) with the same address as before.
How does this work?
A way is to use a proxy contract with a fallback function where each method call/trx is delegated to the implementation contract (which contains all the logic).

A delegate call is similar to a regular call, except that all code is executed in the context of the caller (proxy), not of the callee (implementation). Because of this, a transfer in the implementation contract’s code will transfer the proxy’s balance, and any reads or writes to the contract storage will read or write from the proxy’s storage.
In this approach, users only interact with the proxy contract and we can change the implementation contract while keeping the same proxy contract.

The fallback function will execute on any request, redirecting the request to the implementation and returning the resulting value (using opcodes).
This was a basic explanation which is enough for us to work with upgradeable contracts. In case, you want to dig deep into proxy contract code and different proxy patterns, then check out these posts.
How can I write upgradable smart contracts?
OpenZeppelin provides awesome CLI tools & JS Libraries that take care of all the above complex proxy contracts, linking it to implementation (logic) contract & managing all the contracts you deploy using the CLI for upgradability, out-of-the-box.
The only thing you need to do is to write your contracts, and use OpenZeppelin CLI or Libraries to deploy the contracts.
NOTE: There are a few
Limitations
that you should be aware of, in terms of how you need to write your
contracts and how you should upgrade them. There are also a number of
workarounds these limitations in this
post.